[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report to help us improve\n\n---\n\n(Please follow this template, as doing so saves both you and me a lot of time.\nIssues that don't follow the template may be closed.)\n\n**Description**\n\n(A clear and concise description of what the bug is, e.g.:\nEvery time my mob signs spawn pigs, they turn green and fly away.)\n\n**Reproduce**\n\n(List steps to reproduce, e.g.:\n1. Create a new dungeon with /dxl create\n2. Place [ready] sign, [mob]/pig/0,1/D5 sign\n3. Leave edit mode with /dxl leave\n4. Test with /dxl play\n5. Trigger ready sign and get close to the location of the pig spawn sign)\n\n**Expected behavior**\n\n(A clear and concise description of what you expected to happen, e.g.:\nI wanted the pigs to turn blue and dig mole-like tunnels instead X( )\n\n**Screenshots / GIFs / videos**\n\n(If applicable, add screenshots to help explain your problem.)\n\n**Relevant configuration files**\n\n(The three grave accents mark the beginning and end of a code block. If there are relevant configuration files,\nplease paste them in the lines between the accents sothat others can see if there are any syntax errors.)\n\n```\n\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Please describe the use case or the problem your request is related to.**\n\n(A clear and concise description of what the problem is. Ex. I'm always frustrated when [...];)\n\n**Describe the solution you'd like**\n\n(A clear and concise description of what you want to happen.)\n\n**Describe alternatives you've considered**\n\n(A clear and concise description of any alternative solutions or features you've considered.)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: About things you do not understand in the documentation\n\n---\n\n**The feature you'd like to know more about**\n\n(e.g. game rules)\n\n**Link to the wiki article (in case it exists)**\n\n**What did you already try?**\n\n**Relevant configuration files**\n\n(The three grave accents mark the beginning and end of a code block. If there are relevant configuration files,\nplease paste them in the lines between the accents sothat others can see if there are any syntax errors.)\n\n```\n\n```\n"
  },
  {
    "path": ".gitignore",
    "content": "*apache-maven-*\r\nlicenseheader.txt\r\n*dependency-reduced-pom.xml\r\n*nb-configuration.xml\r\n*target\r\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\n"
  },
  {
    "path": "LICENSE",
    "content": "\n\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n \n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n  \n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "![DungeonsXL](https://erethon.de/resources/logos/DungeonsXL.png)\n\n[![Builds](https://erethon.de/resources/buttons/Builds.png)](http://erethon.de/repo/de/erethon/dungeonsxl/dungeonsxl-dist)\n[![Wiki](https://erethon.de/resources/buttons/Wiki.png)](../../wiki/)\n[![Issues](https://erethon.de/resources/buttons/Issues.png)](../../issues/)\n[![JavaDocs](https://erethon.de/resources/buttons/JavaDocs.png)](http://erethon.de/javadocs/dungeonsxl/)\n[![MCStats](https://erethon.de/resources/buttons/MCStats.png)](http://bstats.org/plugin/bukkit/DungeonsXL/)\n\n[![Build Status](https://travis-ci.com/DRE2N/DungeonsXL.svg?branch=master)](https://travis-ci.com/DRE2N/DungeonsXL) [![codebeat badge](https://codebeat.co/badges/5c57507f-084b-4945-8159-06bf5cf17794)](https://codebeat.co/projects/github-com-dre2n-dungeonsxl-master)\n\n\nDungeonsXL is a server mod that allows you to instantiate worlds.\n\nIts main goal is to offer a way to use a world in a set state multiple times by a player (like for a jump'n'run), a group of players (e.g. for a quest dungeon, an adventure map or a PvE arena) or even by groups of groups of players (e.g. for PvP arenas).\nDungeonsXL also provides custom game mechanics to make these worlds interesting. It might also be helpful if you want players to build something in creative mode quickly and uncomplicated without any influence on their main world data (inventory, levels etc.).\n\n## Features\n* Create as many dungeons as you wish!\n* The instantiation system allows dungeons to be played by multiple groups of players at the same time without clashes.\n* Dungeons are accessable through portals in one of your main worlds. [Read more...](../../wiki/getting-started#entering-the-dungeon)\n* Invite players to edit single dungeons without the need to give them any further permissions. [Read more...](../../wiki/getting-started#editing-the-map)\n* Allow players to build in creative mode safely without any influence to their game progress in the main worlds!\n* Set checkpoints, breakable blocks, triggers, messages and much more through signs in the edit mode. [Read more...](../../wiki/signs)\n* Per dungeon configuration (you should try that after you became familiar with the basics of this plugin). [Read more...](../../wiki/dungeon-configuration)\n* Link multiple floors together to create large dungeons with multiple levels. [Read more...](../../wiki/getting-started#advanced-multi-floor-dungeons-mfds)\n* Use a dungeon as a tutorial and give them a PEX group when they finish it. [Read more...](../../wiki/main-configuration)\n* Players can play the dungeon with their own items or with configurable classes.\n* _The classes support doges!_\n* Mob waves: [Read more...](../../wiki/signs#wave)\n* PvP\n* Time limits\n* A built-in custom mob system and support for MythicMobs. [Read more...](../../wiki/signs#mob)\n* A powerful API: [Read more...](../../wiki/api-tutorial)\n* Different game types allow you to use your maps dynamically for different purposes. [Read more...](../../wiki/game-types)\n* Announcements sothat users can join the next match easily. [Read more...](../../wiki/announcements)\n* Per dungeon resource packs\n* ...and many more!\n\n\n## The concept\n\nIf you want to learn how to use DungeonsXL step by step, please have a look at the [wiki](../../wiki) page [getting started](../../wiki/getting-started).\n\n## Compatibility\n### Server\nDungeonsXL works with Spigot 1.8.8 and higher. However, support for new versions has a higher priority than support for 1.8-1.12. Old builds that support older versions are unusable for production environments. See [here](../../wiki/legacy-support) for detailed information. DungeonsXL works with Spigot and Paper-based server softwares. This does not include Bukkit/Forge hybrids (MCPC+, Cauldron, Mohist, Magma, ...).\n\n### XLib\nDungeonsXL requires [XLib](https://github.com/DRE2N/CaliburnAPI) to run.\n\n### Building information and dependencies\nBuilding DungeonsXL from source requires [Java Development Kit 8 or higher](https://www.azul.com/downloads/?package=jdk#zulu).\n\nBoth XLib and DXL can be built by running the build script in the respective root directory. Use _build.bat_ if you're on Windows and _build.sh_ on Linux, BSD or Mac. Dependencies are downloaded automatically.\n\n### Known incompatibilities\n* Corpses\n* PerWorldInventory\n\nMany incompatibilities can be fixed with [PerWorldPlugins](http://dev.bukkit.org/bukkit-plugins/perworldplugins/) ([fork for 1.8+](https://www.spigotmc.org/resources/perworldplugins-unofficial-update-version.6454/)).\nTry to add the incompatible plugins only to the worlds where you need them.\n"
  },
  {
    "path": "adapter/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-adapter</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-api</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>${spigotVersion.latest}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "adapter/src/main/java/de/erethon/dungeonsxl/adapter/block/BlockAdapter.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.adapter.block;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup.Color;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\n\n/**\n * @author Daniel Saukel\n */\npublic interface BlockAdapter {\n\n    boolean isBedHead(Block block);\n\n    void openDoor(Block block);\n\n    void closeDoor(Block block);\n\n    void setBlockWoolColor(Block block, Color color);\n\n    BlockFace getFacing(Block block);\n\n    void setFacing(Block block, BlockFace facing);\n\n    void setAxis(Block block, boolean z);\n\n}\n"
  },
  {
    "path": "addon/README.md",
    "content": "## DungeonsXL Donors Addon\n\n(C) 2020-2023 Daniel Saukel, All Rights Reserved.\n\nThis module is a plugin with additional features made for donors.\n\nThe GNU LGPLv3 of the API and the GNU GPLv3 license of the other modules do not apply to this module.\n"
  },
  {
    "path": "addon/core/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-addon-core</artifactId>\n    <version>${project.parent.version}</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-addon</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <build>\n        <resources>\n            <resource>\n                <targetPath>.</targetPath>\n                <filtering>true</filtering>\n                <directory>src/main/resources/</directory>\n                <includes>\n                    <include>plugin.yml</include>\n                </includes>\n            </resource>\n        </resources>\n    </build>\n    <dependencies>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot</artifactId>\n            <version>1.16.5-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/DungeonsXXL.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonModule;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxxl.requirement.*;\nimport de.erethon.dungeonsxxl.sign.*;\nimport de.erethon.dungeonsxxl.util.GlowUtil;\nimport de.erethon.xlib.compatibility.Internals;\nimport de.erethon.xlib.plugin.PluginInit;\nimport de.erethon.xlib.plugin.DREPluginSettings;\nimport de.erethon.xlib.util.Registry;\n\n/**\n * @author Daniel Saukel\n */\npublic class DungeonsXXL extends PluginInit implements DungeonModule {\n\n    private static DungeonsXXL instance;\n    private DungeonsXL dxl;\n    private GlowUtil glowUtil;\n\n    public DungeonsXXL() {\n        settings = DREPluginSettings.builder()\n                .internals(Internals.v1_16_R3)\n                .metrics(false)\n                .spigotMCResourceId(-1)\n                .build();\n    }\n\n    @Override\n    public void onEnable() {\n        instance = this;\n        dxl = DungeonsXL.getInstance();\n        glowUtil = new GlowUtil(this);\n    }\n\n    /**\n     * Returns the instance of this plugin.\n     *\n     * @return the instance of this plugin\n     */\n    public static DungeonsXXL getInstance() {\n        return instance;\n    }\n\n    /**\n     * Returns the current {@link de.erethon.dungeonsxl.DungeonsXL} singleton.\n     *\n     * @return the current {@link de.erethon.dungeonsxl.DungeonsXL} singleton\n     */\n    public DungeonsXL getDXL() {\n        return dxl;\n    }\n\n    /**\n     * The loaded instance of GlowUtil.\n     *\n     * @return the loaded instance of GlowUtil\n     */\n    public GlowUtil getGlowUtil() {\n        return glowUtil;\n    }\n\n    @Override\n    public void initRequirements(Registry<String, Class<? extends Requirement>> registry) {\n        registry.add(\"feeItems\", FeeItemsRequirement.class);\n    }\n\n    @Override\n    public void initRewards(Registry<String, Class<? extends Reward>> registry) {\n    }\n\n    @Override\n    public void initSigns(Registry<String, Class<? extends DungeonSign>> registry) {\n        registry.add(\"FIREWORK\", FireworkSign.class);\n        registry.add(\"GLOWINGBLOCK\", GlowingBlockSign.class);\n        registry.add(\"INTERACTWALL\", InteractWallSign.class);\n        registry.add(\"PARTICLE\", ParticleSign.class);\n    }\n\n    @Override\n    public void initGameRules(Registry<String, GameRule> registry) {\n    }\n\n    @Override\n    public void initTriggers(Registry<Character, Class<? extends Trigger>> triggerRegistry) {\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/requirement/FeeItemsRequirement.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport java.util.List;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class FeeItemsRequirement implements Requirement {\n\n    private DungeonsAPI api;\n\n    private List<ItemStack> fee;\n\n    public FeeItemsRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    public List<ItemStack> getFee() {\n        return fee;\n    }\n\n    @Override\n    public void setup(ConfigurationSection config) {\n        fee = api.getXLib().deserializeStackList(config, \"feeItems\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        for (ItemStack stack : fee) {\n            if (!player.getInventory().containsAtLeast(stack, stack.getAmount())) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_FEE_ITEMS + \": \").color(ChatColor.GOLD);\n        boolean first = true;\n        for (ItemStack stack : fee) {\n            String name = stack.getAmount() > 1 ? stack.getAmount() + \" \" : \"\" + api.getXLib().getExItem(stack).getName();\n            ChatColor color = player.getInventory().containsAtLeast(stack, stack.getAmount()) ? ChatColor.GREEN : ChatColor.DARK_RED;\n            if (!first) {\n                builder.append(\", \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n            }\n            builder.append(name).color(color);\n        }\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n        player.getInventory().removeItem(fee.toArray(new ItemStack[]{}));\n    }\n\n    @Override\n    public String toString() {\n        return \"FeeItemsRequirement{items=\" + fee + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/sign/FireworkSign.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxxl.util.FireworkUtil;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class FireworkSign extends Button {\n\n    public FireworkSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Firework\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".firework\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n    }\n\n    @Override\n    public void push() {\n        FireworkUtil.spawnRandom(getSign().getLocation());\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/sign/GlowingBlockSign.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Rocker;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxxl.DungeonsXXL;\nimport de.erethon.dungeonsxxl.world.block.GlowingBlock;\nimport de.erethon.xlib.util.EnumUtil;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Sign;\n\n/**\n * Turns the attached block into a glowing block.\n *\n * @author Daniel Saukel\n */\npublic class GlowingBlockSign extends Rocker {\n\n    private ChatColor color = ChatColor.DARK_RED;\n    private Double time;\n\n    private GlowingBlock glowingBlock;\n\n    public GlowingBlockSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * Returns the glowing block.\n     *\n     * @return the glowing block\n     */\n    public GlowingBlock getGlowingBlock() {\n        return glowingBlock;\n    }\n\n    /**\n     * Returns the color of the glowing block or null if it is a rainbow block.\n     *\n     * @return the color of the glowing block or null if it is a rainbow block\n     */\n    public ChatColor getColor() {\n        return color;\n    }\n\n    @Override\n    public String getName() {\n        return \"GlowingBlock\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".glowingblock\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        if (getLine(1).equalsIgnoreCase(\"RAINBOW\")) {\n            color = null;\n        } else {\n            ChatColor color = EnumUtil.getEnumIgnoreCase(ChatColor.class, getLine(1));\n            if (color != null) {\n                this.color = color;\n            }\n        }\n        try {\n            time = Double.parseDouble(getLine(2));\n        } catch (NumberFormatException exception) {\n        }\n    }\n\n    @Override\n    public void activate() {\n        if (active) {\n            return;\n        }\n\n        ((DGameWorld) getGameWorld()).addGameBlock(\n                glowingBlock = new GlowingBlock(DungeonsXXL.getInstance(), BlockUtilCompat.getAttachedBlock(getSign().getBlock()), color, time));\n        active = true;\n    }\n\n    @Override\n    public void deactivate() {\n        if (!active) {\n            return;\n        }\n\n        glowingBlock.removeGlow();\n        active = false;\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/sign/InteractWallSign.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.sign.passive.InteractSign;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport org.bukkit.block.Sign;\n\n/**\n * This sign adds an interact trigger to an attached block, like a \"suspicious wall\".\n *\n * @author Daniel Saukel\n */\npublic class InteractWallSign extends InteractSign {\n\n    public InteractWallSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"InteractWall\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".interactwall\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        String id = getSign().getLine(1);\n        InteractTrigger trigger = (InteractTrigger) getGameWorld().createTrigger(this, LogicalExpression.parse(\"I\" + id));\n        trigger.setInteractBlock(BlockUtilCompat.getAttachedBlock(getSign().getBlock()));\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/sign/ParticleSign.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Particle;\nimport org.bukkit.block.Sign;\n\n/**\n * Spawns particles.\n *\n * @author Daniel Saukel\n */\npublic class ParticleSign extends Button {\n\n    private Particle particle;\n    private int count;\n    private double offsetX, offsetY, offsetZ;\n    private double extra = 1;\n\n    public ParticleSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Particle\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".particle\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        particle = EnumUtil.getEnumIgnoreCase(Particle.class, getLine(1));\n        if (particle == null) {\n            markAsErroneous(\"Unknown particle type: \" + getLine(1));\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        String[] args = getLine(2).split(\",\");\n        if (args.length == 1) {\n            extra = NumberUtil.parseDouble(args[0], 1);\n        } else if (args.length >= 3) {\n            offsetX = NumberUtil.parseDouble(args[0], 0);\n            offsetX = NumberUtil.parseDouble(args[1], 0);\n            offsetX = NumberUtil.parseDouble(args[2], 0);\n            if (args.length == 4) {\n                extra = NumberUtil.parseDouble(args[3], 1);\n            }\n        }\n    }\n\n    @Override\n    public void push() {\n        getSign().getWorld().spawnParticle(particle, getSign().getLocation(), count, offsetX, offsetY, offsetZ, extra);\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/util/FireworkUtil.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.util;\n\nimport java.util.Random;\nimport org.bukkit.Color;\nimport static org.bukkit.Color.*;\nimport org.bukkit.FireworkEffect;\nimport org.bukkit.Location;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.Firework;\nimport org.bukkit.inventory.meta.FireworkMeta;\n\n/**\n * Util class for randomized fireworks.\n *\n * @author Daniel Saukel\n */\npublic class FireworkUtil {\n\n    private static final Random RANDOM = new Random();\n    private static final Color[] COLORS = {YELLOW, AQUA, BLACK, BLUE, FUCHSIA, GRAY, GREEN, LIME, MAROON, NAVY, OLIVE, ORANGE, PURPLE, RED, SILVER, TEAL, WHITE};\n\n    /**\n     * Spawns a randomized firework.\n     *\n     * @param location the location where the firework is fired\n     * @return the Firework\n     */\n    public static Firework spawnRandom(Location location) {\n        Firework firework = (Firework) location.getWorld().spawnEntity(location, EntityType.FIREWORK);\n        FireworkMeta meta = firework.getFireworkMeta();\n        Random r = new Random();\n        int rt = r.nextInt(4) + 1;\n        FireworkEffect.Type type = FireworkEffect.Type.BALL;\n        if (rt == 1) {\n            type = FireworkEffect.Type.BALL;\n        }\n        if (rt == 2) {\n            type = FireworkEffect.Type.BALL_LARGE;\n        }\n        if (rt == 3) {\n            type = FireworkEffect.Type.BURST;\n        }\n        if (rt == 4) {\n            type = FireworkEffect.Type.CREEPER;\n        }\n        if (rt == 5) {\n            type = FireworkEffect.Type.STAR;\n        }\n        FireworkEffect effect = FireworkEffect.builder().flicker(r.nextBoolean()).withColor(randomColor()).withFade(randomColor()).with(type).trail(r.nextBoolean()).build();\n        meta.addEffect(effect);\n        int rp = r.nextInt(2) + 1;\n        meta.setPower(rp);\n        firework.setFireworkMeta(meta);\n        return firework;\n    }\n\n    private static Color randomColor() {\n        return COLORS[RANDOM.nextInt(COLORS.length - 1)];\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/util/GlowUtil.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.util;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Random;\nimport net.minecraft.server.v1_16_R3.EntityShulker;\nimport net.minecraft.server.v1_16_R3.EntityTypes;\nimport net.minecraft.server.v1_16_R3.Packet;\nimport net.minecraft.server.v1_16_R3.PacketPlayOutEntityDestroy;\nimport net.minecraft.server.v1_16_R3.PacketPlayOutSpawnEntityLiving;\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\nimport org.bukkit.craftbukkit.v1_16_R3.CraftWorld;\nimport org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;\nimport org.bukkit.entity.Player;\nimport org.bukkit.entity.Shulker;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.event.player.PlayerQuitEvent;\nimport org.bukkit.plugin.Plugin;\nimport org.bukkit.potion.PotionEffect;\nimport org.bukkit.potion.PotionEffectType;\nimport org.bukkit.scheduler.BukkitRunnable;\nimport org.bukkit.scoreboard.Team;\n\n/**\n * @author Daniel Saukel\n */\npublic class GlowUtil implements Listener {\n\n    private static final Random RANDOM = new Random();\n\n    private Map<ChatColor, Team> teams = new HashMap<>();\n    private GlowData<org.bukkit.entity.Entity> glowingBlocks = new GlowData<>();\n    private Map<Player, GlowData<net.minecraft.server.v1_16_R3.Entity>> playerGlows = new HashMap<>();\n    private GlowRunnable runnable = new GlowRunnable();\n\n    public GlowUtil(Plugin plugin) {\n        runnable.runTaskTimer(plugin, 0L, 2L);\n        Bukkit.getPluginManager().registerEvents(this, plugin);\n    }\n\n    private Team getTeam(ChatColor color) {\n        if (!teams.containsKey(color)) {\n            Team team = Bukkit.getScoreboardManager().getMainScoreboard().getTeam(\"DXL_\" + color.getChar());\n            if (team == null) {\n                team = Bukkit.getScoreboardManager().getMainScoreboard().registerNewTeam(\"DXL_\" + color.getChar());\n                team.setColor(color);\n            }\n            teams.put(color, team);\n        }\n        return teams.get(color);\n    }\n\n    /**\n     * Adds a colored glow effect to the block that is visible to all players.\n     *\n     * @param block the block\n     * @param color the glow color\n     * @return the spawned entity that provides the glow effect\n     */\n    public org.bukkit.entity.Entity addBlockGlow(Block block, ChatColor color) {\n        Shulker entity = block.getWorld().spawn(new Location(block.getWorld(), block.getX() + .5, block.getY(), block.getZ() + .5), Shulker.class);\n        entity.setAI(false);\n        entity.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0));\n        entity.setInvulnerable(true);\n        addGlow(entity, color);\n        glowingBlocks.put(block, entity);\n        return entity;\n    }\n\n    /**\n     * Adds a packet-level colored glow effect to the block that is only visible to certain players.\n     *\n     * @param block   the block\n     * @param color   the glow color\n     * @param players the players who can see the effect\n     */\n    public void addBlockGlow(Block block, ChatColor color, Player... players) {\n        EntityShulker entity = new EntityShulker(EntityTypes.SHULKER, ((CraftWorld) block.getWorld()).getHandle());\n        entity.setLocation(block.getX() + .5, block.getY(), block.getZ() + .5, 0, 0);\n        entity.setFlag(6, true);\n        entity.setInvisible(true);\n        for (Player player : players) {\n            sendPacket(player, new PacketPlayOutSpawnEntityLiving(entity));\n            if (playerGlows.get(player) == null) {\n                playerGlows.put(player, new GlowData<>());\n            }\n            playerGlows.get(player).put(block, entity);\n        }\n    }\n\n    /**\n     * Adds a rainbow colored glow effect to the block that is visible to all players.\n     *\n     * @param block the block\n     * @return the spawned entity that provides the glow effect\n     */\n    public org.bukkit.entity.Entity addRainbowBlockGlow(Block block) {\n        return addRainbowBlockGlow(block, (Long) null);\n    }\n\n    /**\n     * Adds a rainbow colored glow effect to the block that is visible to all players.\n     * <p>\n     * The task is cancelled automatically when the entity dies.\n     *\n     * @param block      the block\n     * @param cancelTime the time in milliseconds until the glow effect shall end; null = forever\n     * @return the spawned entity that provides the glow effect\n     */\n    public org.bukkit.entity.Entity addRainbowBlockGlow(Block block, Long cancelTime) {\n        Shulker entity = block.getWorld().spawn(new Location(block.getWorld(), block.getX() + .5, block.getY(), block.getZ() + .5), Shulker.class);\n        entity.setAI(false);\n        entity.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0));\n        entity.setInvulnerable(true);\n        glowingBlocks.put(block, entity);\n        addRainbowGlow(entity, cancelTime);\n        return entity;\n    }\n\n    /**\n     * Adds a packet-level rainbow colored glow effect to the block that is only visible to certain players.\n     * <p>\n     * Returns the repeating task that handles color changes.\n     *\n     * @param block   the block\n     * @param players\n     */\n    public void addRainbowBlockGlow(Block block, Player... players) {\n        addRainbowBlockGlow(block, null, players);\n    }\n\n    /**\n     * Adds a packet-level rainbow colored glow effect to the block that is only visible to certain players.\n     * <p>\n     * Returns the repeating task that handles color changes.\n     *\n     * @param block      the block\n     * @param cancelTime the time in milliseconds until the glow effect shall end; null = forever\n     * @param players\n     */\n    public void addRainbowBlockGlow(Block block, Long cancelTime, Player... players) {\n        EntityShulker entity = new EntityShulker(EntityTypes.SHULKER, ((CraftWorld) block.getWorld()).getHandle());\n        entity.setLocation(block.getX() + .5, block.getY(), block.getZ() + .5, 0, 0);\n        entity.setFlag(6, true);\n        entity.setInvisible(true);\n        for (Player player : players) {\n            sendPacket(player, new PacketPlayOutSpawnEntityLiving(entity));\n            if (playerGlows.get(player) == null) {\n                playerGlows.put(player, new GlowData<>());\n            }\n            playerGlows.get(player).put(block, entity);\n        }\n        addRainbowGlow(entity, cancelTime);\n    }\n\n    /**\n     * Removes the glow effect from a glowing block.\n     *\n     * @param block the block\n     */\n    public void removeBlockGlow(Block block) {\n        org.bukkit.entity.Entity bukkitEntity = glowingBlocks.get(block);\n        if (bukkitEntity != null) {\n            bukkitEntity.remove();\n            glowingBlocks.remove(block);\n            runnable.removeEntity(bukkitEntity);\n        }\n\n        for (Entry<Player, GlowData<net.minecraft.server.v1_16_R3.Entity>> entry : playerGlows.entrySet()) {\n            net.minecraft.server.v1_16_R3.Entity nmsEntity = entry.getValue().get(block);\n            if (nmsEntity != null) {\n                sendPacket(entry.getKey(), new PacketPlayOutEntityDestroy(nmsEntity.getId()));\n                runnable.removeEntity(nmsEntity);\n            }\n        }\n    }\n\n    /**\n     * Adds a colored glow effect to an entity and handles its scoreboard team membership.\n     *\n     * @param entity a Bukkit Entity\n     * @param color  the glow color\n     */\n    public void addGlow(org.bukkit.entity.Entity entity, ChatColor color) {\n        getTeam(color).addEntry(asEntry(entity));\n        entity.setGlowing(true);\n    }\n\n    /**\n     * Adds a colored glow effect to an entity and handles its scoreboard team membership.\n     *\n     * @param entity an NMS Entity\n     * @param color  the glow color\n     */\n    public void addGlow(net.minecraft.server.v1_16_R3.Entity entity, ChatColor color) {\n        getTeam(color).addEntry(asEntry(entity));\n        entity.setFlag(6, true);\n    }\n\n    /**\n     * Adds a changing glow effect to an entity.\n     *\n     * @param entity an NMS Entity\n     */\n    public void addRainbowGlow(org.bukkit.entity.Entity entity) {\n        addRainbowGlow(entity, null);\n    }\n\n    /**\n     * Adds a changing glow effect to an entity.\n     *\n     * @param entity     an NMS Entity\n     * @param cancelTime the time in milliseconds until the glow effect shall end; null = forever\n     */\n    public void addRainbowGlow(org.bukkit.entity.Entity entity, Long cancelTime) {\n        entity.setGlowing(true);\n        runnable.addEntity(entity, cancelTime != null ? System.currentTimeMillis() + cancelTime : null);\n    }\n\n    /**\n     * Adds a changing glow effect to an entity.\n     *\n     * @param entity an NMS Entity\n     */\n    public void addRainbowGlow(net.minecraft.server.v1_16_R3.Entity entity) {\n        addRainbowGlow(entity, null);\n    }\n\n    /**\n     * Adds a changing glow effect to an entity.\n     *\n     * @param entity     an NMS Entity\n     * @param cancelTime the time in milliseconds until the glow effect shall end; null = forever\n     */\n    public void addRainbowGlow(net.minecraft.server.v1_16_R3.Entity entity, Long cancelTime) {\n        entity.setFlag(6, true);\n        runnable.addEntity(entity, cancelTime != null ? System.currentTimeMillis() + cancelTime : null);\n    }\n\n    /**\n     * Removes the glow effect from an entity and handles its scoreboard team membership.\n     *\n     * @param entity a Bukkit Entity\n     */\n    public void removeGlow(org.bukkit.entity.Entity entity) {\n        entity.setGlowing(false);\n        teams.values().forEach(t -> t.removeEntry(asEntry(entity)));\n        runnable.removeEntity(entity);\n    }\n\n    /**\n     * Removes the glow effect from an entity and handles its scoreboard team membership.\n     *\n     * @param entity an NMS Entity\n     */\n    public void removeGlow(net.minecraft.server.v1_16_R3.Entity entity) {\n        entity.setFlag(6, false);\n        teams.values().forEach(t -> t.removeEntry(asEntry(entity)));\n        runnable.removeEntity(entity);\n    }\n\n    private static String asEntry(org.bukkit.entity.Entity entity) {\n        return entity instanceof Player ? entity.getName() : entity.getUniqueId().toString();\n    }\n\n    private static String asEntry(net.minecraft.server.v1_16_R3.Entity entity) {\n        return entity instanceof Player ? entity.getName() : entity.getUniqueID().toString();\n    }\n\n    private static void sendPacket(Player player, Packet<?> packet) {\n        ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);\n    }\n\n    @EventHandler\n    public void onBlockBreak(BlockBreakEvent event) {\n        removeBlockGlow(event.getBlock());\n    }\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent event) {\n        playerGlows.remove(event.getPlayer());\n    }\n\n    private class GlowRunnable extends BukkitRunnable {\n\n        private Map<Object, Long> entities = new HashMap<>();\n        private ChatColor color;\n\n        private void addEntity(Object entity, Long cancelTime) {\n            entities.put(entity, cancelTime);\n        }\n\n        private void removeEntity(Object entity) {\n            entities.remove(entity);\n        }\n\n        @Override\n        public void run() {\n            color = ChatColor.values()[RANDOM.nextInt(ChatColor.values().length - 1)];\n            for (Entry<Object, Long> entry : entities.entrySet().toArray(new Entry[entities.size()])) {\n                if (entry.getKey() instanceof org.bukkit.entity.Entity) {\n                    run((org.bukkit.entity.Entity) entry.getKey(), entry.getValue());\n                } else if (entry.getKey() instanceof net.minecraft.server.v1_16_R3.Entity) {\n                    run((net.minecraft.server.v1_16_R3.Entity) entry.getKey(), entry.getValue());\n                }\n            }\n        }\n\n        private void run(org.bukkit.entity.Entity entity, Long cancelTime) {\n            getTeam(color).removeEntry(asEntry(entity));\n            if ((cancelTime != null && System.currentTimeMillis() >= cancelTime) || entity.isDead()) {\n                entities.remove(entity);\n                glowingBlocks.remove(entity);\n                if (!entity.isDead()) {\n                    entity.setGlowing(false);\n                } else {\n                    entity.remove();\n                }\n                return;\n            }\n            getTeam(color).addEntry(asEntry(entity));\n        }\n\n        private void run(net.minecraft.server.v1_16_R3.Entity entity, Long cancelTime) {\n            getTeam(color).removeEntry(asEntry(entity));\n            if (cancelTime != null && System.currentTimeMillis() >= cancelTime) {\n                entities.remove(entity);\n                for (Entry<Player, GlowData<net.minecraft.server.v1_16_R3.Entity>> entry : playerGlows.entrySet()) {\n                    if (!entry.getValue().glowingBlocks.containsValue(entity)) {\n                        continue;\n                    }\n                    Player player = entry.getKey();\n                    sendPacket(player, new PacketPlayOutEntityDestroy(entity.getId()));\n                    entry.getValue().remove(entity);\n                }\n                return;\n            }\n            getTeam(color).addEntry(asEntry(entity));\n        }\n\n    }\n\n    static class GlowData<T> {\n\n        Map<Block, T> glowingBlocks = new HashMap<>();\n\n        T get(Block block) {\n            return glowingBlocks.get(block);\n        }\n\n        void remove(Block block) {\n            glowingBlocks.remove(block);\n        }\n\n        void remove(T entity) {\n            for (Entry<Block, T> entry : glowingBlocks.entrySet().toArray(new Entry[glowingBlocks.size()])) {\n                if (entry.getValue().equals(entity)) {\n                    glowingBlocks.remove(entry.getKey());\n                }\n            }\n        }\n\n        void put(Block block, T entity) {\n            glowingBlocks.put(block, entity);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/java/de/erethon/dungeonsxxl/world/block/GlowingBlock.java",
    "content": "/*\n * Copyright (C) 2020-2026 Daniel Saukel\n *\n * All rights reserved.\n */\npackage de.erethon.dungeonsxxl.world.block;\n\nimport de.erethon.dungeonsxl.world.block.GameBlock;\nimport de.erethon.dungeonsxxl.DungeonsXXL;\nimport de.erethon.dungeonsxxl.util.GlowUtil;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Block;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class GlowingBlock extends GameBlock {\n\n    private GlowUtil glowUtil;\n    \n    public GlowingBlock(DungeonsXXL plugin, Block block, ChatColor color, Double time) {\n        super(plugin.getDXL(), block);\n        glowUtil = plugin.getGlowUtil();\n\n        Long millis;\n        if (time != null) {\n            millis = (long) (time * 1000);\n        } else {\n            millis = null;\n        }\n\n        if (color != null) {\n            glowUtil.addBlockGlow(block, color);\n            if (millis != null) {\n                new BukkitRunnable() {\n                    @Override\n                    public void run() {\n                        removeGlow();\n                    }\n                }.runTaskLater(plugin, millis / 50);\n            }\n        } else {\n            glowUtil.addRainbowBlockGlow(block, millis);\n        }\n    }\n\n    public void removeGlow() {\n        glowUtil.removeBlockGlow(block);\n    }\n\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "addon/core/src/main/resources/plugin.yml",
    "content": "name: DungeonsXXL\nmain: de.erethon.dungeonsxxl.DungeonsXXL\nversion: ${project.version}${buildNo}\nauthor: Daniel Saukel\ndescription: ${project.description}\nwebsite: ${project.url}\ndepend: [DungeonsXL]\n"
  },
  {
    "path": "addon/dist/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-addon-dist</artifactId>\n    <version>${project.parent.version}</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-addon</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <build>\n        <finalName>${project.artifactId}-${project.version}${buildNo}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.6.1</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-addon-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "addon/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-addon</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>pom</packaging>\n    <name>DungeonsXXL</name>\n    <url>https://dre2n.github.io</url>\n    <description>Create BETTER custom dungeons and adventure maps with ease!</description>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <modules>\n        <module>core</module>\n        <module>dist</module>\n    </modules>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-dist</artifactId>\n            <version>0.19-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "api/LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "api/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-api</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>3.12.0</version>\n                <configuration>\n                    <doclint>all,-missing</doclint>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <phase>install</phase>\n                        <goals>\n                            <goal>javadoc</goal>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n    <dependencies>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>${spigotVersion.latest}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/DungeonModule.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api;\n\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.xlib.util.Registry;\n\n/**\n * Class that manages initialization of several registries.\n * <p>\n * Addons should implement this interface and add their feature implementations to the registry in the respective method.\n *\n * @author Daniel Saukel\n */\npublic interface DungeonModule {\n\n    /**\n     * Initializes the {@link de.erethon.dungeonsxl.api.Requirement requirement} registry.\n     *\n     * @param requirementRegistry the registry\n     */\n    void initRequirements(Registry<String, Class<? extends Requirement>> requirementRegistry);\n\n    /**\n     * Initializes the {@link de.erethon.dungeonsxl.api.Reward reward} registry.\n     *\n     * @param rewardRegistry the registry\n     */\n    void initRewards(Registry<String, Class<? extends Reward>> rewardRegistry);\n\n    /**\n     * Initializes the {@link de.erethon.dungeonsxl.api.sign.DungeonSign dungeon sign} registry.\n     *\n     * @param signRegistry the registry\n     */\n    void initSigns(Registry<String, Class<? extends DungeonSign>> signRegistry);\n\n    /**\n     * Initializes the {@link de.erethon.dungeonsxl.api.dungeon.GameRule game rule} registry.\n     *\n     * @param gameRuleRegistry the registry\n     */\n    void initGameRules(Registry<String, GameRule> gameRuleRegistry);\n\n    /**\n     * Initializes the {@link de.erethon.dungeonsxl.api.trigger.Trigger trigger} registry.\n     *\n     * @param triggerRegistry the registry\n     */\n    void initTriggers(Registry<Character, Class<? extends Trigger>> triggerRegistry);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/DungeonsAPI.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\nimport de.erethon.dungeonsxl.api.player.GroupAdapter;\nimport de.erethon.dungeonsxl.api.player.PlayerCache;\nimport de.erethon.dungeonsxl.api.player.PlayerClass;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.mob.ExMob;\nimport de.erethon.xlib.util.Registry;\nimport java.io.File;\nimport java.util.Collection;\nimport org.bukkit.World;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.plugin.Plugin;\n\n/**\n * The API main interface.\n *\n * @author Daniel Saukel\n */\npublic interface DungeonsAPI extends Plugin {\n\n    static final File PLUGIN_ROOT = new File(\"plugins/DungeonsXL\");\n    static final File BACKUPS = new File(PLUGIN_ROOT, \"backups\");\n    static final File LANGUAGES = new File(PLUGIN_ROOT, \"languages\");\n    static final File MAPS = new File(PLUGIN_ROOT, \"maps\");\n    static final File PLAYERS = new File(PLUGIN_ROOT, \"players\");\n    static final File SCRIPTS = new File(PLUGIN_ROOT, \"scripts\");\n    static final File CLASSES = new File(SCRIPTS, \"classes\");\n    static final File DUNGEONS = new File(SCRIPTS, \"dungeons\");\n\n    /**\n     * Returns the loaded instance of XLib.\n     *\n     * @return the loaded instance of XLib\n     */\n    XLib getXLib();\n\n    /**\n     * Returns a cache of player wrapper objects.\n     *\n     * @return a cache of player wrapper objects\n     */\n    PlayerCache getPlayerCache();\n\n    /**\n     * Returns a cache of Game objects.\n     *\n     * @return a cache of Game objects\n     */\n    Collection<Game> getGameCache();\n\n    /**\n     * Returns a registry of the loaded classes.\n     *\n     * @return a registry of the loaded classes\n     */\n    Registry<String, PlayerClass> getClassRegistry();\n\n    /**\n     * Returns a registry of the sign types.\n     *\n     * @return a registry of the sign types\n     */\n    Registry<String, Class<? extends DungeonSign>> getSignRegistry();\n\n    /**\n     * Returns a registry of the requirement types.\n     *\n     * @return a registry of the requirement types\n     */\n    Registry<String, Class<? extends Requirement>> getRequirementRegistry();\n\n    /**\n     * Returns a registry of the reward types.\n     *\n     * @return a registry of the reward types\n     */\n    Registry<String, Class<? extends Reward>> getRewardRegistry();\n\n    /**\n     * Returns a registry of the dungeons.\n     *\n     * @return a registry of the dungeons\n     */\n    Registry<String, Dungeon> getDungeonRegistry();\n\n    /**\n     * Returns a registry of the resources worlds.\n     *\n     * @return a registry of the resources worlds\n     */\n    Registry<String, ResourceWorld> getMapRegistry();\n\n    /**\n     * Returns a cache of the instance worlds.\n     *\n     * @return a cache of the instance worlds\n     */\n    Registry<Integer, InstanceWorld> getInstanceCache();\n\n    /**\n     * Returns a registry of the game rules.\n     *\n     * @return a registry of the game rules\n     */\n    Registry<String, GameRule> getGameRuleRegistry();\n\n    /**\n     * Returns a registry of the triggers.\n     *\n     * @return a registry of the triggers\n     */\n    Registry<Character, Class<? extends Trigger>> getTriggerRegistry();\n\n    /**\n     * Returns a registry of the external mob providers.\n     *\n     * @return a registry of the external mob providers\n     */\n    Registry<String, ExternalMobProvider> getExternalMobProviderRegistry();\n\n    /**\n     * Returns a cache of the player groups.\n     *\n     * @return a cache of the player groups\n     */\n    Registry<String, PlayerGroup> getGroupCache();\n\n    /**\n     * Registers a DungeonModule.\n     *\n     * @param module the module to register\n     */\n    void registerModule(DungeonModule module);\n\n    /**\n     * Makes DungeonsXL track external group and synchronize them with its own groups.\n     *\n     * @param groupAdapter the group adapter to register\n     */\n    void registerGroupAdapter(GroupAdapter groupAdapter);\n\n    /* Object initialization */\n    /**\n     * Creates a new group.\n     *\n     * @param leader the leader\n     * @return a new group\n     */\n    PlayerGroup createGroup(Player leader);\n\n    /**\n     * Creates a new group.\n     *\n     * @param leader the leader\n     * @param color  the color that represents the group and sets the name\n     * @return a new group or null if values are invalid\n     */\n    PlayerGroup createGroup(Player leader, PlayerGroup.Color color);\n\n    /**\n     * Creates a new group.\n     *\n     * @param leader the leader\n     * @param name   the group's name - must be unique\n     * @return a new group or null if values are invalid\n     */\n    PlayerGroup createGroup(Player leader, String name);\n\n    /**\n     * Creates a new group.\n     *\n     * @param leader  the leader\n     * @param dungeon the dungeon to play\n     * @return a new group or null if values are invalid\n     */\n    PlayerGroup createGroup(Player leader, Dungeon dungeon);\n\n    /**\n     * Creates a new group.\n     *\n     * @param leader  the leader\n     * @param members the group members with or without the leader\n     * @param name    the name of the group\n     * @param dungeon the dungeon to play\n     * @return a new group or null if values are invalid\n     */\n    PlayerGroup createGroup(Player leader, Collection<Player> members, String name, Dungeon dungeon);\n\n    /**\n     * Wraps the given {@link LivingEntity} object in a {@link DungeonMob} object.\n     *\n     * @param entity    the entity\n     * @param gameWorld the game world where the entity is\n     * @param triggerId the identifier used in mob triggers\n     * @return the wrapped DungeonMob\n     */\n    DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, String triggerId);\n\n    /**\n     * Wraps the given {@link LivingEntity} object in a {@link DungeonMob} object.\n     *\n     * @param entity    the entity\n     * @param gameWorld the game world where the entity is\n     * @param type      the ExMob type of the entity\n     * @return the wrapped DungeonMob\n     */\n    DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, ExMob type);\n\n    /**\n     * Wraps the given {@link LivingEntity} object in a {@link DungeonMob} object.\n     *\n     * @param entity    the entity\n     * @param gameWorld the game world where the entity is\n     * @param type      the ExMob type of the entity\n     * @param triggerId the identifier used in mob triggers\n     * @return the wrapped DungeonMob\n     */\n    DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, ExMob type, String triggerId);\n\n    /* Getters */\n    /**\n     * Returns an existing {@link DungeonMob} object that wraps the given {@link LivingEntity} object or null if none exists.\n     *\n     * @param entity the entity\n     * @return an existing {@link DungeonMob} object that wraps the given {@link LivingEntity} object or null if none exists\n     */\n    DungeonMob getDungeonMob(LivingEntity entity);\n\n    /**\n     * Returns the group the player is a member of or null if he is in none.\n     *\n     * @param member the player\n     * @return the group the player is a member of or null if he is in none\n     */\n    PlayerGroup getPlayerGroup(Player member);\n\n    /**\n     * Returns the game the given player plays.\n     *\n     * @param player the player\n     * @return the game the given player plays\n     */\n    Game getGame(Player player);\n\n    /**\n     * Returns the game played in the given instance world.\n     *\n     * @param world the instance world\n     * @return the game played in the given instance world\n     */\n    Game getGame(World world);\n\n    /**\n     * Returns the GameWorld that wraps the given instance world.\n     *\n     * @param world the instance world\n     * @return the GameWorld that wraps the given instance world\n     */\n    GameWorld getGameWorld(World world);\n\n    /**\n     * Returns the EditWorld that wraps the given instance world.\n     *\n     * @param world the instance world\n     * @return the EditWorld that wraps the given instance worl\n     */\n    EditWorld getEditWorld(World world);\n\n    /**\n     * Returns if the given world is an instance.\n     *\n     * @param world the world\n     * @return if the given world is an instance\n     */\n    boolean isInstance(World world);\n\n    /**\n     * Returns if the given item stack is a dungeon item.\n     * <p>\n     * Dungeon items are items that are removed from the inventory when the dungeon is finished.\n     *\n     * @param itemStack the item stack\n     * @return if the given item stack is a dungeon item\n     */\n    boolean isDungeonItem(ItemStack itemStack);\n\n    /**\n     * Sets the given item stack to be a dungeon item and returns a copy with the updated state.\n     * <p>\n     * Dungeon items are items that are removed from the inventory when the dungeon is finished.\n     *\n     * @param itemStack   the item stack\n     * @param dungeonItem if the item stack\n     * @return a copy of the item stack that is a dungeon item\n     */\n    ItemStack setDungeonItem(ItemStack itemStack, boolean dungeonItem);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/Requirement.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api;\n\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * Something a player needs to fulfill in order to be allowed to start the game (= trigger a ready sign).\n *\n * @author Daniel Saukel\n */\npublic interface Requirement {\n\n    /**\n     * Sets up the requirement from the given requirements {@link de.erethon.dungeonsxl.api.dungeon.GameRule} section.\n     *\n     * @param config the requirements config section\n     */\n    void setup(ConfigurationSection config);\n\n    /**\n     * Returns if the given player fulfills the requirements. If true, this lets him start the game (= trigger a ready sign).\n     *\n     * @param player the player\n     * @return if the given player fulfills the requirements\n     */\n    boolean check(Player player);\n\n    /**\n     * Returns the message that informs the player if they fulfill the requirement.\n     *\n     * @param player the player who will receive the message\n     * @return the error message that is sent to the player when they do not fulfill the requirement\n     */\n    BaseComponent[] getCheckMessage(Player player);\n\n    /**\n     * This is fired after the {@link #check(Player)} has been accepted. It demands the requirement from the given player. This may be empty for a \"key\" or may\n     * take something away for a \"fee\" requirement.\n     *\n     * @param player the player\n     */\n    void demand(Player player);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/Reward.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api;\n\nimport org.bukkit.entity.Player;\n\n/**\n * Something players are given when they successfully finish a {@link de.erethon.dungeonsxl.api.dungeon.Dungeon}.\n *\n * @see de.erethon.dungeonsxl.api.player.PlayerGroup#getRewards()\n * @author Daniel Saukel\n */\npublic interface Reward {\n\n    /**\n     * Gives the reward to the given player.\n     *\n     * @param player the player\n     */\n    void giveTo(Player player);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/BuildMode.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Player;\n\n/**\n * Checks for whether a block may be broken.\n *\n * @author Daniel Saukel\n */\npublic interface BuildMode {\n\n    /**\n     * Stores the pre-set breaking rules.\n     */\n    static class Registry {\n        /**\n         * Entry keys must be lowercase.\n         */\n        public static final Map<String, BuildMode> ENTRIES = new HashMap<>();\n\n        static {\n            ENTRIES.put(\"true\", TRUE);\n            ENTRIES.put(\"false\", FALSE);\n            ENTRIES.put(\"placed\", PLACED);\n        }\n    }\n\n    /**\n     * All blocks except for protected ones may be broken.\n     */\n    static final BuildMode TRUE = (Player player, GameWorld gameWorld, Block block) -> true;\n    /**\n     * Blocks may not be broken.\n     */\n    static final BuildMode FALSE = (Player player, GameWorld gameWorld, Block block) -> false;\n    /**\n     * Blocks placed by players may be broken.\n     */\n    static final BuildMode PLACED = (Player player, GameWorld gameWorld, Block block) -> gameWorld.getPlacedBlocks().contains(block);\n\n    /**\n     * Returns if the block can be broken or placed by the player.\n     * <p>\n     * The plugin protects dungeon signs before checking this.\n     *\n     * @param player    the player who breaks or places the block\n     * @param gameWorld the world the block is in\n     * @param block     the block\n     * @return if the block can be broken or placed by the player\n     */\n    boolean check(Player player, GameWorld gameWorld, Block block);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/CollectionGameRule.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport com.google.common.base.Verify;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport java.util.Collection;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * A {@link GameRule} where the value is a {@link java.util.Collection}.\n *\n * @param <T> the type of the collection\n * @param <V> the type of the game rule value\n * @author Daniel Saukel\n */\npublic class CollectionGameRule<T, V extends Collection<T>> extends GameRule<V> {\n\n    protected Copier<V> copier;\n\n    /**\n     * @param key          the configuration key of the game rule\n     * @param defaultValue the default value that is used when nothing is set; not null\n     * @param copier       a method to copy the collection\n     */\n    public CollectionGameRule(String key, V defaultValue, Copier<V> copier) {\n        super(null, key, defaultValue);\n        Verify.verifyNotNull(defaultValue, \"defaultValue must not be null\");\n        this.copier = copier;\n    }\n\n    /**\n     * @param key          the configuration key of the game rule\n     * @param defaultValue the default value that is used when nothing is set\n     * @param reader       a functional interface that loads the value from config\n     * @param copier       a method to copy the collection\n     */\n    public CollectionGameRule(String key, V defaultValue, ConfigReader<V> reader, Copier<V> copier) {\n        super(null, key, defaultValue, reader);\n        this.copier = copier;\n    }\n\n    /**\n     * This implementation uses more expensive casting + catching the ClassCastException.\n     * Developers should consider doing that themselves instead of wasting this cast.\n     *\n     * @param value the value\n     * @return if the given value is an instance of {@link V}\n     */\n    @Override\n    public boolean isValidValue(Object value) {\n        try {\n            V v = (V) value;\n            return true;\n        } catch (ClassCastException exception) {\n            return false;\n        }\n    }\n\n    @Override\n    public V fromConfig(DungeonsAPI api, GameRuleContainer container, ConfigurationSection config) {\n        Object value = config.get(getKey());\n        if (reader != null) {\n            V v = reader.read(api, value);\n            setStateWithoutNull(container, v);\n            return v;\n        }\n\n        V v;\n        try {\n            v = (V) value;\n        } catch (ClassCastException exception) {\n            return null;\n        }\n        setStateWithoutNull(container, v);\n        return v;\n    }\n\n    private void setStateWithoutNull(GameRuleContainer container, V v) {\n        while (v != null && v.contains(null)) {\n            v.remove(null);\n        }\n        container.setState(this, v);\n    }\n\n    @Override\n    public void merge(GameRuleContainer overriding, GameRuleContainer subsidiary, GameRuleContainer writeTo) {\n        V writeToState = writeTo.getState(this);\n        V write = writeToState != null ? copier.copy(writeTo.getState(this)) : null;\n\n        if (subsidiary != writeTo) {\n            V subsidiaryState = subsidiary.getState(this);\n            if (subsidiaryState != null) {\n                if (write == null) {\n                    write = copier.copy(subsidiaryState);\n                } else {\n                    write.addAll(subsidiaryState);\n                }\n            }\n        }\n\n        if (overriding != writeTo) {\n            V overridingState = overriding.getState(this);\n            if (overridingState != null) {\n                if (write == null) {\n                    write = copier.copy(overridingState);\n                } else {\n                    write.addAll(overridingState);\n                }\n            }\n        }\n        writeTo.setState(this, write);\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/ConfigReader.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.mob.ExMob;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.bukkit.block.Block;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * A functional interface to deserialize a raw value read from a configuration.\n *\n * @param <V> the type of the object to read\n * @author Daniel Saukel\n */\n@FunctionalInterface\npublic interface ConfigReader<V> {\n\n    /**\n     * Reads a set of XLib items.\n     */\n    static final ConfigReader<Set<ExItem>> EX_ITEM_SET_READER = (api, value) -> {\n        if (!(value instanceof Collection)) {\n            return null;\n        }\n        Set<ExItem> set = new HashSet<>();\n        for (Object entry : (Collection) value) {\n            set.add(api.getXLib().getExItem(entry));\n        }\n        return set;\n    };\n    /**\n     * Reads a set of XLib mobs.\n     */\n    static final ConfigReader<Set<ExMob>> EX_MOB_SET_READER = (api, value) -> {\n        if (!(value instanceof Collection)) {\n            return null;\n        }\n        Set<ExMob> set = new HashSet<>();\n        for (Object entry : (Collection) value) {\n            set.add(api.getXLib().getExMob(entry));\n        }\n        return set;\n    };\n    /**\n     * Reads a map of XLib items as tool keys and a set of XLib items as block values.\n     */\n    static final ConfigReader<Map<ExItem, HashSet<ExItem>>> TOOL_BLOCK_MAP_READER = (api, value) -> {\n        if (!(value instanceof ConfigurationSection)) {\n            return null;\n        }\n        ConfigurationSection section = (ConfigurationSection) value;\n        Map<ExItem, HashSet<ExItem>> map = new HashMap<>();\n        for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {\n            ExItem tool = api.getXLib().getExItem(entry.getKey());\n            if (tool == null) {\n                continue;\n            }\n            HashSet<ExItem> blocks = new HashSet<>();\n            blocks.addAll(api.getXLib().deserializeExItemList(section, entry.getKey()));\n            map.put(tool, blocks);\n        }\n        return map;\n    };\n    static final ConfigReader<BuildMode> BUILD_MODE_READER = (api, value) -> {\n        if (value instanceof Boolean) {\n            return (Boolean) value ? BuildMode.TRUE : BuildMode.FALSE;\n        } else if (value instanceof String) {\n            return BuildMode.Registry.ENTRIES.get(((String) value).toLowerCase());\n        } else if (value instanceof List) {\n            return (Player p, GameWorld w, Block b) -> ((List) value).contains(VanillaItem.get(b.getType()).getId());\n        } else {\n            Map<ExItem, HashSet<ExItem>> whitelist = TOOL_BLOCK_MAP_READER.read(api, value);\n            if (whitelist == null) {\n                return null;\n            }\n            return (Player p, GameWorld w, Block b) -> {\n                ExItem type = VanillaItem.get(b.getType());\n                ExItem breakTool = api.getXLib().getExItem(p.getItemInHand());\n                return whitelist.containsKey(type)\n                        && (whitelist.get(type) == null\n                        || whitelist.get(type).isEmpty()\n                        || whitelist.get(type).contains(breakTool));\n            };\n        }\n    };\n\n    /**\n     * Reads a game rule state from the configuration.\n     *\n     * @param api   the DungeonsAPI instance\n     * @param value the configuration object. This is the object received from using {@link org.bukkit.configuration.ConfigurationSection#get(String)}\n     *              with the String being {@link GameRule#getKey()}.\n     * @return the game rule state read from configuration\n     */\n    V read(DungeonsAPI api, Object value);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/Copier.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\n/**\n * Copies an object.\n *\n * @param <T> the type of the object to copy\n * @author Daniel Saukel\n */\n@FunctionalInterface\npublic interface Copier<T> {\n\n    /**\n     * Returns a copy of the original.\n     *\n     * @param original the original\n     * @return a copy of the original\n     */\n    T copy(T original);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/Dungeon.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport java.util.List;\n\n/**\n * A dungeon consists of floors and settings including its game rules.\n * <p>\n * MFD = multiple floor dungeon; SFD = single floor dungeon.\n *\n * @author Daniel Saukel\n */\npublic interface Dungeon {\n\n    /**\n     * Returns the name.\n     *\n     * @return the name\n     */\n    String getName();\n\n    /**\n     * Sets the name to the given value.\n     *\n     * @param name the name\n     */\n    void setName(String name);\n\n    /**\n     * Returns if this dungeon has multiple floors.\n     *\n     * @return if this dungeon has multiple floors\n     */\n    boolean isMultiFloor();\n\n    /**\n     * Returns the map to instantiate.\n     * <p>\n     * This method is the same as {@link #getStartFloor()} but a bit more intuitive for SFDs.\n     *\n     * @return the map to instantiate\n     */\n    default ResourceWorld getMap() {\n        return getStartFloor();\n    }\n\n    /**\n     * Returns the first floor of this dungeon.\n     *\n     * @return the first floor of this dungeon\n     */\n    ResourceWorld getStartFloor();\n\n    /**\n     * Sets the first floor of this dungeon.\n     *\n     * @param startFloor the startFloor to set\n     */\n    void setStartFloor(ResourceWorld startFloor);\n\n    /**\n     * Returns the last floor of this dungeon or null if this is an SFD.\n     *\n     * @return the last floor of this dungeon or null if this is an SFD\n     */\n    ResourceWorld getEndFloor();\n\n    /**\n     * Sets the last floor of this MFD.\n     *\n     * @param endFloor the last floor\n     */\n    void setEndFloor(ResourceWorld endFloor);\n\n    /**\n     * Returns a list of the floors without start and end floor.\n     *\n     * @return a list of the floors without start and end floor\n     */\n    List<ResourceWorld> getFloors();\n\n    /**\n     * Adds the given floor.\n     *\n     * @param resource the resource to add\n     */\n    void addFloor(ResourceWorld resource);\n\n    /**\n     * Removes the given floor.\n     *\n     * @param resource the resource to remove\n     */\n    void removeFloor(ResourceWorld resource);\n\n    /**\n     * Returns the amount of floors in this dungeon including start and end floor.\n     * <p>\n     * This may be less than the size of {@link #getFloors()} + 2 if not all floors from the list are used.\n     *\n     * @return the amount of floors in this dungeon including start and end floor\n     */\n    int getFloorCount();\n\n    /**\n     * Sets the amount of floors that shall be played.\n     *\n     * @param floorCount the amount of floors to set\n     */\n    void setFloorCount(int floorCount);\n\n    /**\n     * Returns if floors cannot be played once if floors are selected randomly from the list.\n     *\n     * @return the removeWhenPlayed if floors cannot be played once if floors are selected randomly from the list\n     */\n    boolean getRemoveWhenPlayed();\n\n    /**\n     * Sets if floors cannot be played once if floors are selected randomly from the list.\n     *\n     * @param removeWhenPlayed if floors cannot be played once if floors are selected randomly from the list\n     */\n    void setRemoveWhenPlayed(boolean removeWhenPlayed);\n\n    /**\n     * The values from this game rule container will override all values of the game rule containers of the dungeon's maps.\n     *\n     * @return the override values\n     */\n    GameRuleContainer getOverrideValues();\n\n    /**\n     * The values from this game rule container will be overriden by values of the game rule containers of the dungeon's maps. They will however still override\n     * the values from the main config.\n     *\n     * @return the default values\n     */\n    GameRuleContainer getDefaultValues();\n\n    /**\n     * Returns true if the floor is either in the floors list or the start / end floor.\n     *\n     * @param resource the ResourceWorld to check\n     * @return true if the floor is either in the floors list or the start / end floor.\n     */\n    default boolean containsFloor(ResourceWorld resource) {\n        if (isMultiFloor()) {\n            return getFloors().contains(resource) || getStartFloor().equals(resource) || getEndFloor().equals(resource);\n        } else {\n            return getMap().equals(resource);\n        }\n    }\n\n    /**\n     * Returns true if the floor is either in the floors list or the start / end floor.\n     *\n     * @param mapName the name of the map to check\n     * @return true if the floor is either in the floors list or the start / end floor.\n     */\n    default boolean containsFloor(String mapName) {\n        for (ResourceWorld world : getFloors()) {\n            if (world.getName().equals(mapName)) {\n                return true;\n            }\n        }\n        return getStartFloor().getName().equals(mapName) || getEndFloor().getName().equals(mapName);\n    }\n\n    /**\n     * Returns the rules of this game.\n     * <p>\n     * This is not necessarily represented 1:1 by a config file because it is usually merged together through {@link #setupRules()}.\n     *\n     * @return the rules of this game\n     */\n    GameRuleContainer getRules();\n\n    /**\n     * Sets the rules of the game.\n     *\n     * @param rules the rules\n     */\n    void setRules(GameRuleContainer rules);\n\n    /**\n     * Sets up the rules with the following priority: 1. Game type 2. Dungeon config: Override values 3. Floor config 4. Dungeon config: Default values 5. Main\n     * config: Default values 6. The default values\n     */\n    void setupRules();\n\n    /**\n     * Returns false if there are errors in the setup; true if not.\n     *\n     * @return false if there are errors in the setup; true if not\n     */\n    boolean isSetupCorrect();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/Game.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport java.util.Collection;\nimport java.util.List;\nimport org.bukkit.entity.Player;\n\n/**\n * Handles the rules of playing in a dungeon.\n * <p>\n * Tracks the progress of groups in a dungeon and handles their interaction with each other.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: wave and kill counter methods\npublic interface Game {\n\n    /**\n     * Returns if this is a tutorial game.\n     *\n     * @return if this is a tutorial game\n     */\n    boolean isTutorial();\n\n    /**\n     * Sets if this is a tutorial game\n     *\n     * @param tutorial if this is a tutorial game\n     */\n    void setTutorial(boolean tutorial);\n\n    /**\n     * Returns a read-only List of the groups that are playing this game.\n     *\n     * @return a read-only List of the groups that are playing this game\n     */\n    List<PlayerGroup> getGroups();\n\n    /**\n     * Adds the given group to this game.\n     *\n     * @param group the group\n     */\n    void addGroup(PlayerGroup group);\n\n    /**\n     * Removes the given group from this game.\n     *\n     * @param group the group\n     */\n    void removeGroup(PlayerGroup group);\n\n    /**\n     * Returns if the group has started (=if the ready sign has been triggered).\n     *\n     * @return if the group has started\n     */\n    boolean hasStarted();\n\n    /**\n     * Sets the status of the game to have started / not yet started.\n     *\n     * @param started if the game has started\n     */\n    void setStarted(boolean started);\n\n    /**\n     * Returns the game instance in which this game takes place.\n     *\n     * @return the game instance in which this game takes place\n     */\n    GameWorld getWorld();\n\n    /**\n     * Sets the game instance in which this game takes place.\n     *\n     * @param gameWorld the game instance in which this game takes place\n     */\n    void setWorld(GameWorld gameWorld);\n\n    /**\n     * Returns if the game has rewards.\n     *\n     * @return if the game has rewards\n     */\n    boolean hasRewards();\n\n    /**\n     * Sets if the game has rewards.\n     *\n     * @param enabled if the game has rewards\n     */\n    void setRewards(boolean enabled);\n\n    /**\n     * Returns the rules of the dungeon of this game.\n     * <p>\n     * This is not necessarily represented 1:1 by a config file because it is usually merged together through {@link Dungeon#setupRules()}.\n     *\n     * @return the rules of the dungeon of this game\n     */\n    default GameRuleContainer getRules() {\n        return getDungeon().getRules();\n    }\n\n    /**\n     * Returns a read-only List of the remaining floors to play.\n     *\n     * @return a read-only List of the remaining floors to play\n     */\n    List<ResourceWorld> getUnplayedFloors();\n\n    /**\n     * Adds a floor to the list of floors to play.\n     *\n     * @param unplayedFloor the resource world of the floor\n     * @return if the addition was successful\n     */\n    boolean addUnplayedFloor(ResourceWorld unplayedFloor);\n\n    /**\n     * Removes a floor from the list of floors to play.\n     *\n     * @param unplayedFloor the resource world of the floor\n     * @param force         if the floor shall be removed even if the {@link #getDungeon() dungeon}'s floors are not to be\n     *                      {@link Dungeon#getRemoveWhenPlayed() removed when played.}\n     * @return if the removal was successful\n     */\n    boolean removeUnplayedFloor(ResourceWorld unplayedFloor, boolean force);\n\n    /**\n     * Returns the resource of the next floor to play.\n     *\n     * @return the resource of the next floor to play\n     */\n    ResourceWorld getNextFloor();\n\n    /**\n     * Sets the next floor to play.\n     *\n     * @param floor the resource world of the floor\n     */\n    void setNextFloor(ResourceWorld floor);\n\n    /**\n     * Returns the amount of played floors in this game.\n     *\n     * @return the amount of played floors in this game\n     */\n    int getFloorCount();\n\n    /**\n     * Returns the dungeon that \"hosts\" this game.\n     *\n     * @return the dungeon that \"hosts\" this game\n     */\n    Dungeon getDungeon();\n\n    /**\n     * Returns the players playing the game.\n     *\n     * @return the players playing the game\n     */\n    Collection<Player> getPlayers();\n\n    /**\n     * Returns true if there are no groups in this game; false if not.\n     *\n     * @return true if there are no groups in this game; false if not\n     */\n    boolean isEmpty();\n\n    /**\n     * Returns and, if necessary, instantiates the game world.\n     *\n     * @param ignoreLimit if the instance limit set in the main config shall be ignored\n     * @return the game world\n     */\n    GameWorld ensureWorldIsLoaded(boolean ignoreLimit);\n\n    /**\n     * Starts the game. This is what happens when the ready sign is triggered by everyone.\n     *\n     * @return if the game has started correctly\n     */\n    boolean start();\n\n    /**\n     * Deletes this game.\n     */\n    void delete();\n\n    /**\n     * Returns true if all groups of the game have finished it; false if not.\n     *\n     * @return true if all groups of the game have finished it; false if not\n     */\n    default boolean isFinished() {\n        return getGroups().stream().allMatch(PlayerGroup::isFinished);\n    }\n\n    /**\n     * Sends a message to each player in each group.\n     *\n     * @param message the message. Supports color codes\n     */\n    default void sendMessage(String message) {\n        getGroups().forEach(g -> g.sendMessage(message));\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameGoal.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.xlib.util.EnumUtil;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * A game goal defines what the players have to do in order to finish the game.\n *\n * @author Daniel Saukel\n */\npublic class GameGoal extends GameRuleContainer {\n\n    /**\n     * Score used for capture the flag and similar game types.\n     */\n    public static final GameRule<Integer> INITIAL_SCORE = new GameRule<>(Integer.class, \"initialScore\", 3);\n    /**\n     * The amount of goals to score before the game ends. -1 = not used.\n     */\n    public static final GameRule<Integer> SCORE_GOAL = new GameRule<>(Integer.class, \"scoreGoal\", -1);\n    /**\n     * The time left to finish the game; -1 if no timer is used.\n     */\n    public static final GameRule<Integer> TIME_TO_FINISH = new GameRule<>(Integer.class, \"timeToFinish\", -1);\n\n    /**\n     * The default game goal: {@link Type#END} without TIME_TO_FINISH\n     */\n    public static final GameGoal DEFAULT = new GameGoal(Type.END);\n\n    static {\n        DEFAULT.setState(TIME_TO_FINISH, TIME_TO_FINISH.getDefaultValue());\n    }\n\n    /**\n     * The reader to deserialize a game goal from a configuration.\n     */\n    public static final ConfigReader<GameGoal> READER = (api, value) -> {\n        if (!(value instanceof ConfigurationSection)) {\n            return DEFAULT;\n        }\n        ConfigurationSection config = (ConfigurationSection) value;\n        Type type = EnumUtil.getEnumIgnoreCase(Type.class, config.getString(\"type\", \"END\"));\n        GameGoal goal = new GameGoal(type);\n        for (GameRule rule : type.getComponents()) {\n            rule.fromConfig(api, goal, config);\n            if (!goal.rules.containsKey(rule)) {\n                goal.setState(rule, rule.getDefaultValue());\n            }\n        }\n        return goal;\n    };\n\n    private Type type;\n\n    public GameGoal(Type type) {\n        this.type = type;\n    }\n\n    /**\n     * Returns the type of the game goal.\n     *\n     * @return the type\n     */\n    public Type getType() {\n        return type;\n    }\n\n    /**\n     * Determines the behavior of the game goal and which settings apply to it.\n     */\n    public enum Type {\n        /**\n         * The default goal. The game ends when the end is reached.\n         */\n        END(TIME_TO_FINISH),\n        /**\n         * The game ends when a player dies and only one group is left.\n         */\n        LAST_MAN_STANDING,\n        /**\n         * SCORE_GOAL = -1: The game does not end. Instead, the goal is to survive as long as possible to beat a highscore.\n         * <p>\n         * SCORE_GOAL > 0: The game ends when a group reachs a specific score.\n         * <p>\n         * TIME_TO_FINISH != -1: The game ends after a specific time. The goal is to get the highest score until then.\n         */\n        SCORE(INITIAL_SCORE, SCORE_GOAL, TIME_TO_FINISH),\n        /**\n         * The game ends after a specific time. The goal is to survive until then.\n         */\n        TIME_SURVIVAL(TIME_TO_FINISH);\n\n        private GameRule[] components;\n\n        Type(GameRule... components) {\n            this.components = components;\n        }\n\n        /**\n         * Returns an array of the game rules that apply to game goals of this type.\n         *\n         * @return an array of the game rules that apply to game goals of this type\n         */\n        public GameRule[] getComponents() {\n            return components;\n        }\n\n        /**\n         * Returns whether the given game rule applies to game goals of this type.\n         *\n         * @param component the game rule\n         * @return whether the given game rule applies to game goals of this type\n         */\n        public boolean hasComponent(GameRule component) {\n            for (GameRule c : components) {\n                if (c == component) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.mob.ExMob;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.bukkit.Difficulty;\nimport org.bukkit.GameMode;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * Represents a game rule for a {@link Game}.\n *\n * @param <V> the type of the game rule value\n * @author Daniel Saukel\n */\npublic class GameRule<V> {\n\n    /**\n     * Shall players play the dungeon with their own items or do you want to use classes?\n     */\n    public static final GameRule<Boolean> KEEP_INVENTORY_ON_ENTER = new GameRule<>(Boolean.class, \"keepInventoryOnEnter\", false);\n    /**\n     * Shall players keep their inventory when they leave the dungeon without succeeding?\n     */\n    public static final GameRule<Boolean> KEEP_INVENTORY_ON_ESCAPE = new GameRule<>(Boolean.class, \"keepInventoryOnEscape\", false);\n    /**\n     * Shall players keep their inventory when they finish the dungeon?\n     */\n    public static final GameRule<Boolean> KEEP_INVENTORY_ON_FINISH = new GameRule<>(Boolean.class, \"keepInventoryOnFinish\", false);\n    /**\n     * Shall players lose their items when they die (do not mix up this with \"onEscape\"!)?\n     */\n    public static final GameRule<Boolean> KEEP_INVENTORY_ON_DEATH = new GameRule<>(Boolean.class, \"keepInventoryOnDeath\", true);\n    /**\n     * Shall players reset their inventory to their chosen class when respawning?\n     */\n    public static final GameRule<Boolean> RESET_CLASS_INVENTORY_ON_RESPAWN = new GameRule<>(Boolean.class, \"resetClassInventoryOnRespawn\", false);\n    /**\n     * The location where the players spawn when they leave the dungeon without succeeding.\n     */\n    public static final GameRule<String> ESCAPE_LOCATION = new GameRule<>(String.class, \"escapeLocation\", null);\n    /**\n     * The location where the players spawn when they finish the dungeon.\n     */\n    public static final GameRule<String> FINISH_LOCATION = new GameRule<>(String.class, \"finishLocation\", null);\n    /**\n     * The goal of the game that defines what makes it end.\n     */\n    public static final GameRule<GameGoal> GAME_GOAL = new GameRule<>(GameGoal.class, \"gameGoal\", GameGoal.DEFAULT, GameGoal.READER);\n    /**\n     * The Vanilla game mode.\n     */\n    public static final GameRule<GameMode> GAME_MODE = new GameRule<>(GameMode.class, \"gameMode\", GameMode.SURVIVAL);\n    /**\n     * The Vanilla difficulty.\n     */\n    public static final GameRule<Difficulty> DIFFICULTY = new GameRule<>(Difficulty.class, \"difficulty\", Difficulty.NORMAL);\n    /**\n     * If the food levels of the players change.\n     */\n    public static final GameRule<Boolean> FOOD_LEVEL = new GameRule<>(Boolean.class, \"foodLevel\", true);\n    /**\n     * Sets if death screens are enabled. If false, players that would have died are healed and teleported to the respawn location;\n     * their inventory and experience are dropped if {@link #KEEP_INVENTORY_ON_DEATH} is set to false.\n     */\n    public static final GameRule<Boolean> DEATH_SCREEN = new GameRule<>(Boolean.class, \"deathScreen\", false);\n    /**\n     * If players may fly.\n     */\n    public static final GameRule<Boolean> FLY = new GameRule<>(Boolean.class, \"fly\", false);\n    /**\n     * If players can build and destroy blocks in this world.\n     */\n    public static final GameRule<BuildMode> BREAK_BLOCKS = new GameRule<>(BuildMode.class, \"breakBlocks\", BuildMode.FALSE, ConfigReader.BUILD_MODE_READER);\n    /**\n     * A blacklist of block types players cannot interact with.\n     */\n    public static final GameRule<Map<ExItem, HashSet<ExItem>>> INTERACTION_BLACKLIST\n            = new MapGameRule<>(\"interactionBlacklist\", new HashMap<>(), ConfigReader.TOOL_BLOCK_MAP_READER, HashMap::new);\n    /**\n     * A list of all entity types that shall be protected from damage.\n     */\n    public static final GameRule<Set<ExMob>> DAMAGE_PROTECTED_ENTITIES = new CollectionGameRule<>(\"damageProtectedEntities\", new HashSet<>(), ConfigReader.EX_MOB_SET_READER, HashSet::new);\n    /**\n     * A list of all entity types that shall be protected from interaction.\n     */\n    public static final GameRule<Set<ExMob>> INTERACTION_PROTECTED_ENTITIES = new CollectionGameRule<>(\"interactionProtectedEntities\", new HashSet<>(), ConfigReader.EX_MOB_SET_READER, HashSet::new);\n    /**\n     * If blocks may be placed.\n     */\n    public static final GameRule<BuildMode> PLACE_BLOCKS = new GameRule<>(BuildMode.class, \"placeBlocks\", BuildMode.FALSE, ConfigReader.BUILD_MODE_READER);\n    /**\n     * A set of blocks that do not fade.\n     *\n     * @see <a href=\"https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/block/BlockFadeEvent.html\">org.bukkit.event.block.BlockFadeEvent</a>\n     */\n    public static final GameRule<Set<ExItem>> BLOCK_FADE_DISABLED = new CollectionGameRule<>(\"blockFadeDisabled\", new HashSet<>(), ConfigReader.EX_ITEM_SET_READER, HashSet::new);\n    /**\n     * This does what the doFireTick Vanilla game rule does.\n     */\n    public static final GameRule<Boolean> FIRE_TICK = new GameRule<>(Boolean.class, \"fireTick\", false);\n    /**\n     * If it should rain permanently in the dungeon.\n     * <p>\n     * true = permanent rain; false = permanent sun; leaving this out = random weather like in vanilla Minecraft\n     */\n    public static final GameRule<Boolean> RAIN = new GameRule<>(Boolean.class, \"rain\", null);\n    /**\n     * Thunderstorms.\n     *\n     * @see #RAIN\n     */\n    public static final GameRule<Boolean> THUNDER = new GameRule<>(Boolean.class, \"thunder\", null);\n    /**\n     * The time ticks (to be used like in the vanilla /time command).\n     */\n    public static final GameRule<Long> TIME = new GameRule<>(Long.class, \"time\", null);\n    /**\n     * PvP\n     */\n    public static final GameRule<Boolean> PLAYER_VERSUS_PLAYER = new GameRule<>(Boolean.class, \"playerVersusPlayer\", false);\n    /**\n     * Friendly fire refers just to members of the same group.\n     */\n    public static final GameRule<Boolean> FRIENDLY_FIRE = new GameRule<>(Boolean.class, \"friendlyFire\", false);\n    /**\n     * Amount of lives a player initially has when he enters a dungeon.\n     */\n    public static final GameRule<Integer> INITIAL_LIVES = new GameRule<>(Integer.class, \"initialLives\", -1);\n    /**\n     * Alternatively to {@link #INITIAL_LIVES player lives}, you can use group lives.\n     */\n    public static final GameRule<Integer> INITIAL_GROUP_LIVES = new GameRule<>(Integer.class, \"initialGroupLives\", -1);\n    /**\n     * When loot may be taken away out of the dungeon again.\n     */\n    public static final GameRule<Integer> TIME_TO_NEXT_LOOT = new GameRule<>(Integer.class, \"timeToNextLoot\", 0);\n    /**\n     * The cooldown between two mob waves.\n     */\n    public static final GameRule<Integer> TIME_TO_NEXT_WAVE = new GameRule<>(Integer.class, \"timeToNextWave\", 10);\n    /**\n     * Time until a player is kicked out of a group after he leaves the server.\n     */\n    public static final GameRule<Integer> TIME_UNTIL_KICK_OFFLINE_PLAYER = new GameRule<>(Integer.class, \"timeUntilKickOfflinePlayer\", 0);\n    /**\n     * A list of requirements. Note that requirements will be ignored if the player has the dxl.ignorerequirements permission node.\n     */\n    public static final GameRule<List<Requirement>> REQUIREMENTS = new CollectionGameRule<>(\"requirements\", new ArrayList<>(), (api, value) -> {\n        if (!(value instanceof ConfigurationSection)) {\n            return null;\n        }\n        ConfigurationSection section = (ConfigurationSection) value;\n        List<Requirement> requirements = new ArrayList<>();\n        for (String key : section.getValues(false).keySet()) {\n            Class<? extends Requirement> clss = api.getRequirementRegistry().get(key);\n            if (clss == null) {\n                MessageUtil.log(api, \"&4Could not find requirement named \\\"\" + key + \"\\\".\");\n                continue;\n            }\n            try {\n                Constructor constructor = clss.getConstructor(DungeonsAPI.class);\n                if (constructor == null) {\n                    MessageUtil.log(api, \"&4Requirement \\\"\" + key + \"\\\" is not implemented properly with a (DungeonsAPI) constructor.\");\n                    continue;\n                }\n                Requirement requirement = (Requirement) constructor.newInstance(api);\n                requirement.setup(section);\n                requirements.add(requirement);\n            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException\n                    | IllegalArgumentException | InvocationTargetException exception) {\n                MessageUtil.log(api, \"&4Requirement \\\"\" + key + \"\\\" is not implemented properly with a (DungeonsAPI) constructor.\");\n            }\n        }\n        return requirements;\n    }, ArrayList::new);\n    /**\n     * This can be used to give rewards. The default implementation does not do this at the moment.\n     */\n    public static final GameRule<List<Reward>> REWARDS = new CollectionGameRule<>(\"rewards\", new ArrayList<>(), (api, value) -> {\n        if (!(value instanceof ConfigurationSection)) {\n            return null;\n        }\n        ConfigurationSection section = (ConfigurationSection) value;\n        List<Reward> rewards = new ArrayList<>();\n        for (String key : section.getValues(false).keySet()) {\n            Class<? extends Reward> clss = api.getRewardRegistry().get(key);\n            if (clss == null) {\n                MessageUtil.log(api, \"&4Could not find reward named \\\"\" + key + \"\\\".\");\n                continue;\n            }\n            try {\n                Constructor constructor = clss.getConstructor(DungeonsAPI.class);\n                if (constructor == null) {\n                    MessageUtil.log(api, \"&4Reward \\\"\" + key + \"\\\" is not implemented properly with a (DungeonsAPI) constructor.\");\n                    continue;\n                }\n                Reward reward = (Reward) constructor.newInstance(api);\n                // reward.setup();\n                rewards.add(reward);\n            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException\n                    | IllegalArgumentException | InvocationTargetException exception) {\n                MessageUtil.log(api, \"&4Reward \\\"\" + key + \"\\\" is not implemented properly with a (DungeonsAPI) constructor.\");\n            }\n        }\n        return rewards;\n    }, ArrayList::new);\n    /**\n     * These commands can be used by all players if they are in the dungeon. DXL commands like /dxl leavecan be used by default.\n     */\n    public static final GameRule<List<String>> GAME_COMMAND_WHITELIST = new CollectionGameRule<>(\"gameCommandWhitelist\", new ArrayList<>(), ArrayList::new);\n    /**\n     * A list of permissions players get while they play the game. The permissions get removed as soon as the player leaves the game. Requires Vault and a\n     * permissions plugin like PermissionsEx.\n     */\n    public static final GameRule<List<String>> GAME_PERMISSIONS = new CollectionGameRule<>(\"gamePermissions\", new ArrayList<>(), ArrayList::new);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<String> TITLE = new GameRule<>(String.class, \"title.title\", null);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<String> SUBTITLE = new GameRule<>(String.class, \"title.subtitle\", null);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<String> ACTION_BAR = new GameRule<>(String.class, \"title.actionBar\", null);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<String> CHAT = new GameRule<>(String.class, \"title.chat\", null);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<Integer> TITLE_FADE_IN = new GameRule<>(Integer.class, \"title.fadeIn\", 20);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<Integer> TITLE_FADE_OUT = new GameRule<>(Integer.class, \"title.fadeOut\", 20);\n    /**\n     * Use this to replace the default ready / new floor message. If titles are deactivated in the main config, this is not going to work.\n     */\n    public static final GameRule<Integer> TITLE_SHOW = new GameRule<>(Integer.class, \"title.show\", 60);\n    /**\n     * Messages; also to be created with /dxl msg\n     */\n    public static final GameRule<Map<Integer, String>> MESSAGES = new MapGameRule<>(\"messages\", new HashMap<>(), (api, value) -> {\n        if (!(value instanceof ConfigurationSection)) {\n            return null;\n        }\n        ConfigurationSection section = (ConfigurationSection) value;\n        Map<Integer, String> map = new HashMap<>();\n        for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {\n            int id = NumberUtil.parseInt(entry.getKey(), -1);\n            if (id == -1) {\n                continue;\n            }\n            if (!(entry.getValue() instanceof String)) {\n                continue;\n            }\n            map.put(id, (String) entry.getValue());\n        }\n        return map;\n    }, HashMap::new);\n    /**\n     * Items you cannot drop or destroy.\n     */\n    public static final GameRule<Set<ExItem>> SECURE_OBJECTS = new CollectionGameRule<>(\"secureObjects\", new HashSet<>(), ConfigReader.EX_ITEM_SET_READER, HashSet::new);\n    /**\n     * If group tags are used.\n     */\n    public static final GameRule<Boolean> GROUP_TAG_ENABLED = new GameRule<>(Boolean.class, \"groupTagEnabled\", false);\n    /**\n     * If Citizens NPCs should be copied to the native registry.\n     */\n    public static final GameRule<Boolean> USE_NATIVE_CITIZENS_REGISTRY = new GameRule<>(Boolean.class, \"useNativeCitizensRegistry\", false);\n    /**\n     * If mobs shall drop experience or a whitelist of mobs that drop experience, while all others do not.\n     */\n    public static final GameRule<Object> MOB_EXP_DROPS = new GameRule(Object.class, \"mobExpDrops\", false,\n            (api, value) -> value instanceof Boolean ? value : ConfigReader.EX_MOB_SET_READER.read(api, value)\n    );\n    /**\n     * If mobs shall drop items or a whitelist of mobs that drop items, while all others do not.\n     */\n    public static final GameRule<Object> MOB_ITEM_DROPS = new GameRule(Object.class, \"mobItemDrops\", false,\n            (api, value) -> value instanceof Boolean ? value : ConfigReader.EX_MOB_SET_READER.read(api, value)\n    );\n\n    /**\n     * An array of all game rules that exist natively in DungeonsXL.\n     */\n    public static final GameRule[] VALUES = values();\n    /**\n     * A container of all rules with their default value. This is used internally as the most subsidiary container that fills missing rules if they are not set.\n     */\n    public static final GameRuleContainer DEFAULT_VALUES = new GameRuleContainer();\n\n    static {\n        for (GameRule rule : VALUES) {\n            DEFAULT_VALUES.setState(rule, rule.getDefaultValue());\n        }\n    }\n\n    private static GameRule[] values() {\n        Field[] fields = GameRule.class.getFields();\n        GameRule[] values = new GameRule[fields.length - 2];\n        int i = 0;\n        for (Field field : fields) {\n            try {\n                Object object = field.get(null);\n                if (object instanceof GameRule) {\n                    values[i++] = (GameRule) object;\n                }\n            } catch (IllegalArgumentException | IllegalAccessException exception) {\n                exception.printStackTrace();\n            }\n        }\n        return values;\n    }\n\n    protected Class<V> type;\n    protected ConfigReader<V> reader;\n    private String key;\n    private V defaultValue;\n\n    /**\n     * @param type         the class of V\n     * @param key          the configuration key of the game rule\n     * @param defaultValue the default value that is used when nothing is set\n     */\n    public GameRule(Class<V> type, String key, V defaultValue) {\n        this.type = type;\n        this.key = key;\n        this.defaultValue = defaultValue;\n    }\n\n    /**\n     * @param type         the class of V\n     * @param key          the configuration key of the game rule\n     * @param defaultValue the default value that is used when nothing is set\n     * @param reader       a functional interface that loads the value from config\n     */\n    public GameRule(Class<V> type, String key, V defaultValue, ConfigReader<V> reader) {\n        this(type, key, defaultValue);\n        this.reader = reader;\n    }\n\n    /**\n     * Returns the configuration key of the game rule.\n     *\n     * @return the configuration key of the game rule\n     */\n    public String getKey() {\n        return key;\n    }\n\n    /**\n     * Returns the value used if nothing is specified by a game rule provider.\n     *\n     * @return the value used if nothing is specified by a game rule provider\n     */\n    public V getDefaultValue() {\n        return defaultValue;\n    }\n\n    /**\n     * Returns if the given value is an instance of {@link V}.\n     *\n     * @param value the value\n     * @return if the given value is an instance of {@link V}\n     */\n    public boolean isValidValue(Object value) {\n        return type.isInstance(value);\n    }\n\n    /**\n     * Returns the state of the game rule fetched from the config.\n     * <p>\n     * If the type of this game rule is an enum, Strings as config values that are the {@link Enum#name()} of an enum value are converted automatically.\n     *\n     * @param api       the API instance\n     * @param container the game rule container whose state is to be set\n     * @param config    the config to fetch the value from\n     * @return the value\n     */\n    public V fromConfig(DungeonsAPI api, GameRuleContainer container, ConfigurationSection config) {\n        Object value = config.get(getKey());\n        if (reader != null) {\n            V v = reader.read(api, value);\n            container.setState(this, v);\n            return v;\n        }\n\n        if (Enum.class.isAssignableFrom(type)) {\n            if (!(value instanceof String)) {\n                return null;\n            }\n            value = EnumUtil.getEnumIgnoreCase((Class<? extends Enum>) type, (String) value);\n        }\n\n        if (isValidValue(value)) {\n            container.setState(this, (V) value);\n            return (V) value;\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Compares the state attached to the game rule of two GameRuleContainers.\n     * <p>\n     * This may be overriden if necessary, for example if the value is a {@link java.util.Collection} and the desired behavior is to merge the values instead of\n     * keeping the overriding one.\n     *\n     * @param overriding the state of this container will by default be copied to the \"writeTo\" container if it is not null\n     * @param subsidiary the state of this container will by default be copied to the \"writeTo\" container if the state of the \"overriding\" container is null\n     * @param writeTo    the state of the game rule will be set to the one of either \"overriding\" or \"subsidiary\". This container may be == to one of the\n     *                   others.\n     */\n    public void merge(GameRuleContainer overriding, GameRuleContainer subsidiary, GameRuleContainer writeTo) {\n        V overridingValue = overriding.getState(this);\n        V subsidiaryValue = subsidiary.getState(this);\n        writeTo.setState(this, overridingValue != null ? overridingValue : subsidiaryValue);\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{key=\" + key + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRuleContainer.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * A container for {@link GameRule}s.\n *\n * @author Daniel Saukel\n */\npublic class GameRuleContainer {\n\n    protected Map<GameRule<?>, Object> rules;\n\n    /**\n     * Initializes an emtpy GameRuleContainer.\n     */\n    public GameRuleContainer() {\n        rules = new HashMap<>();\n    }\n\n    /**\n     * Copies a GameRuleContainer.\n     *\n     * @param container the container to copy\n     */\n    public GameRuleContainer(GameRuleContainer container) {\n        rules = new HashMap<>(container.rules);\n    }\n\n    /**\n     * Returns the state of the GameRule or UNDEFINED_STATE if it is not defined\n     *\n     * @param <V>  the type of the value of the rule\n     * @param rule the rule\n     * @return the state of the rule\n     */\n    public <V> V getState(GameRule<V> rule) {\n        if (!rules.containsKey(rule)) {\n            return null;\n        } else {\n            return (V) rules.get(rule);\n        }\n    }\n\n    /**\n     * Sets the state of the GameRule.Set it to null to remove the rule from the map sothat a subsidiary provider can set it.\n     *\n     * @param <V>   the type of the value of the rule\n     * @param rule  the rule\n     * @param state the new state of the rule in this container\n     */\n    public <V> void setState(GameRule<V> rule, V state) {\n        if (state == null) {\n            rules.remove(rule);\n        } else if (rule.isValidValue(state)) {\n            rules.put(rule, state);\n        } else {\n            throw new IllegalArgumentException(\"state is not a valid value for rule \" + rule.getKey());\n        }\n    }\n\n    /**\n     * Removes the rule from the map sothat a subsidiary container can set it.\n     *\n     * @param rule the GameRule to unset\n     */\n    public void unsetState(GameRule<?> rule) {\n        rules.remove(rule);\n    }\n\n    /**\n     * Fills the values that are not yet set with values from a subsidiary container.\n     *\n     * @param subsidiary the GameRules that override the values that are null.\n     */\n    public void merge(GameRuleContainer subsidiary) {\n        subsidiary.rules.entrySet().forEach(e -> e.getKey().merge(this, subsidiary, this));\n    }\n\n    @Override\n    public String toString() {\n        return \"GameRuleContainer{\" + rules + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/dungeon/MapGameRule.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.dungeon;\n\nimport com.google.common.base.Verify;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport java.util.Map;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * A {@link GameRule} where the value is a {@link java.util.Map}.\n *\n * @param <TK> the type of the map key\n * @param <TV> the type of the map value\n * @param <V>  the type of the game rule value\n * @author Daniel Saukel\n */\npublic class MapGameRule<TK, TV, V extends Map<TK, TV>> extends GameRule<V> {\n\n    protected Copier<V> copier;\n\n    /**\n     * @param key          the configuration key of the game rule\n     * @param defaultValue the default value that is used when nothing is set; not null\n     * @param reader       a functional interface that loads the value from config\n     * @param copier       a method to copy the map\n     */\n    public MapGameRule(String key, V defaultValue, ConfigReader<V> reader, Copier<V> copier) {\n        super(null, key, defaultValue, reader);\n        Verify.verifyNotNull(defaultValue, \"defaultValue must not be null\");\n        this.copier = copier;\n    }\n\n    /**\n     * This implementation uses more expensive casting + catching the ClassCastException.\n     * Developers should consider doing that themselves instead of wasting this cast.\n     *\n     * @param value the value\n     * @return if the given value is an instance of {@link V}\n     */\n    @Override\n    public boolean isValidValue(Object value) {\n        try {\n            V v = (V) value;\n            return true;\n        } catch (ClassCastException exception) {\n            return false;\n        }\n    }\n\n    @Override\n    public V fromConfig(DungeonsAPI api, GameRuleContainer container, ConfigurationSection config) {\n        if (reader == null) {\n            return null;\n        }\n\n        V v = reader.read(api, config.getConfigurationSection(getKey()));\n        if (v == null) {\n            return null;\n        }\n        v.remove(null); // Do not allow null values\n        container.setState(this, v);\n        return v;\n    }\n\n    @Override\n    public void merge(GameRuleContainer overriding, GameRuleContainer subsidiary, GameRuleContainer writeTo) {\n        V writeToState = writeTo.getState(this);\n        V write = writeToState != null ? copier.copy(writeTo.getState(this)) : null;\n\n        V subsidiaryState = subsidiary.getState(this);\n        if (subsidiaryState != null) {\n            if (write == null) {\n                write = copier.copy(subsidiaryState);\n            } else {\n                write.putAll(subsidiaryState);\n            }\n        }\n\n        V overridingState = overriding.getState(this);\n        if (overridingState != null) {\n            if (write == null) {\n                write = copier.copy(overridingState);\n            } else {\n                write.putAll(overridingState);\n            }\n        }\n        writeTo.setState(this, write);\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/DataReloadEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event;\n\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.Event;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when the plugin is reloaded with /dxl reload.\n *\n * @author Daniel Saukel\n */\npublic class DataReloadEvent extends Event implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupCollectRewardEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group collects a reward.\n * <p>\n * In the default implementation, this happens when a player opens a reward chest.\n *\n * @author Daniel Saukel\n */\npublic class GroupCollectRewardEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GamePlayer collector;\n    private Reward reward;\n\n    public GroupCollectRewardEvent(PlayerGroup group, GamePlayer collector, Reward reward) {\n        super(group);\n        this.collector = collector;\n        this.reward = reward;\n    }\n\n    /**\n     * Returns the player who collected the reward.\n     * <p>\n     * Note that this may be null if addons add a way to give rewards that cannot be attributed to one collector.\n     *\n     * @return the player who collected the reward\n     */\n    public GamePlayer getCollector() {\n        return collector;\n    }\n\n    /**\n     * Returns the reward the group collected.\n     *\n     * @return the reward the group collected\n     */\n    public Reward getReward() {\n        return reward;\n    }\n\n    /**\n     * Sets the reward the group collected.\n     *\n     * @param reward the reward\n     */\n    public void setReward(Reward reward) {\n        this.reward = reward;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; collector=\" + collector + \"; reward=\" + reward + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupCreateEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group is created explicitly or implicitly.\n *\n * @author Daniel Saukel\n */\npublic class GroupCreateEvent extends GroupEvent implements Cancellable {\n\n    /**\n     * The reason why the group is created.\n     */\n    public enum Cause {\n\n        ANNOUNCER,\n        COMMAND,\n        /**\n         * When a group is created to mirror the state of a party plugin.\n         *\n         * @see de.erethon.dungeonsxl.api.player.GroupAdapter\n         */\n        GROUP_ADAPTER,\n        GROUP_SIGN,\n        TUTORIAL,\n        /**\n         * When a group is created by an addon.\n         */\n        CUSTOM\n\n    }\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GlobalPlayer creator;\n    private Cause cause;\n\n    public GroupCreateEvent(PlayerGroup group, GlobalPlayer creator, Cause cause) {\n        super(group);\n        this.creator = creator;\n        this.cause = cause;\n    }\n\n    /**\n     * Returns the player who created the group.\n     *\n     * @return the player who created the group\n     */\n    public GlobalPlayer getCreator() {\n        return creator;\n    }\n\n    /**\n     * Returns the cause for the group creation.\n     *\n     * @return the cause for the group creation\n     */\n    public Cause getCause() {\n        return cause;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; creator=\" + creator + \"; cause=\" + cause + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupDisbandEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group is disbanded.\n *\n * @author Daniel Saukel\n */\npublic class GroupDisbandEvent extends GroupEvent implements Cancellable {\n\n    public enum Cause {\n\n        COMMAND,\n        DUNGEON_FINISHED,\n        GROUP_ADAPTER,\n        GROUP_IS_EMPTY,\n        LOST,\n        CUSTOM\n\n    }\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GlobalPlayer disbander;\n    private Cause cause;\n\n    public GroupDisbandEvent(PlayerGroup group, Cause cause) {\n        super(group);\n        this.cause = cause;\n    }\n\n    public GroupDisbandEvent(PlayerGroup group, GlobalPlayer disbander, Cause cause) {\n        super(group);\n        this.disbander = disbander;\n        this.cause = cause;\n    }\n\n    /**\n     * The player who disbanded the group.\n     * <p>\n     * This is null if the cause is {@link Cause#DUNGEON_FINISHED}, {@link Cause#GROUP_ADAPTER}, {@link Cause#LOST} or {@link Cause#CUSTOM}.\n     *\n     * @return the player who disbanded the group\n     */\n    public GlobalPlayer getDisbander() {\n        return disbander;\n    }\n\n    /**\n     * Returns the cause for the group deletion.\n     *\n     * @return the cause for the group deletion\n     */\n    public Cause getCause() {\n        return cause;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; disbander=\" + disbander + \"; cause=\" + cause + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving DungeonsXL groups.\n *\n * @author Daniel Saukel\n */\npublic abstract class GroupEvent extends Event {\n\n    protected PlayerGroup group;\n\n    protected GroupEvent(PlayerGroup group) {\n        this.group = group;\n    }\n\n    /**\n     * Returns the group involved in this event.\n     *\n     * @return the group involved in this event\n     */\n    public PlayerGroup getGroup() {\n        return group;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupFinishDungeonEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group finishs a {@link Dungeon}, which means the end floor of a dungeon.\n * <p>\n * Do not confuse this with {@link de.erethon.dungeonsxl.api.event.player.GamePlayerFinishEvent}. GamePlayerFinishEvent is fired when a player triggers an end\n * sign, while GroupFinishDungeonEvent is triggered when all group members have triggered the end sign and the game actually ends.\n *\n * @author Daniel Saukel\n */\npublic class GroupFinishDungeonEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private Dungeon dungeon;\n\n    public GroupFinishDungeonEvent(PlayerGroup group, Dungeon dungeon) {\n        super(group);\n        this.dungeon = dungeon;\n    }\n\n    /**\n     * Returns the dungeon the group was playing.\n     *\n     * @return the dungeon the group was playing\n     */\n    public Dungeon getDungeon() {\n        return dungeon;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; dungeon=\" + dungeon + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupFinishFloorEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group finishs a dungeon floor.\n *\n * @author Daniel Saukel\n */\npublic class GroupFinishFloorEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GameWorld finished;\n    private ResourceWorld next;\n\n    public GroupFinishFloorEvent(PlayerGroup group, GameWorld finished, ResourceWorld next) {\n        super(group);\n        this.finished = finished;\n        this.next = next;\n    }\n\n    /**\n     * Returns the game world that was just finished.\n     *\n     * @return the game world that was just finished\n     */\n    public GameWorld getFinished() {\n        return finished;\n    }\n\n    /**\n     * Returns the resource world of the next floor.\n     *\n     * @return the resource world of the next floor\n     */\n    public ResourceWorld getNext() {\n        return next;\n    }\n\n    /**\n     * Sets the next floor to load.\n     * <p>\n     * If one has already been loaded because another group finished the floor earlier, this will not do anything.\n     *\n     * @param next the next floor to load\n     */\n    public void setNext(ResourceWorld next) {\n        this.next = next;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; finished=\" + finished + \"; next=\" + next + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupPlayerJoinEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player joins a DungeonsXL group.\n *\n * @author Daniel Saukel\n */\npublic class GroupPlayerJoinEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GlobalPlayer player;\n    private boolean creator;\n\n    public GroupPlayerJoinEvent(PlayerGroup group, GlobalPlayer player, boolean creator) {\n        super(group);\n        this.player = player;\n        this.creator = creator;\n    }\n\n    /**\n     * Returns the player who is joining the group.\n     *\n     * @return the player who is joining the group\n     */\n    public GlobalPlayer getPlayer() {\n        return player;\n    }\n\n    /**\n     * Returns if the player is the creator of the group.\n     *\n     * @return if the player is the creator of the group\n     */\n    public boolean isCreator() {\n        return creator;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; player=\" + player + \"; creator=\" + creator + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupPlayerKickEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player is kicked out of a group.\n *\n * @author Daniel Saukel\n */\npublic class GroupPlayerKickEvent extends GroupEvent implements Cancellable {\n\n    public enum Cause {\n\n        COMMAND,\n        /**\n         * When the player is kicked because he does not have any lives left.\n         */\n        DEATH,\n        /**\n         * When a player is kicked from a group to mirror the state of a party plugin.\n         *\n         * @see de.erethon.dungeonsxl.api.player.GroupAdapter\n         */\n        GROUP_ADAPTER,\n        OFFLINE,\n        /**\n         * When the time for the group to reach a certain state expired.\n         */\n        TIME_EXPIRED,\n        CUSTOM\n\n    }\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GlobalPlayer player;\n    private Cause cause;\n\n    public GroupPlayerKickEvent(PlayerGroup group, GlobalPlayer player, Cause cause) {\n        super(group);\n        this.player = player;\n        this.cause = cause;\n    }\n\n    /**\n     * Returns the player who is joining the group.\n     *\n     * @return the player who is joining the group\n     */\n    public GlobalPlayer getPlayer() {\n        return player;\n    }\n\n    /**\n     * Returns the cause of the kick.\n     *\n     * @return the cause of the kick\n     */\n    public Cause getCause() {\n        return cause;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; player=\" + player + \"; cause=\" + cause + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupPlayerLeaveEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player leaves a group.\n *\n * @author Daniel Saukel\n */\npublic class GroupPlayerLeaveEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GlobalPlayer player;\n\n    public GroupPlayerLeaveEvent(PlayerGroup group, GlobalPlayer player) {\n        super(group);\n        this.player = player;\n    }\n\n    /**\n     * Returns the player who left the group.\n     *\n     * @return the player who left the group\n     */\n    public GlobalPlayer getPlayer() {\n        return player;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; player=\" + player + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupScoreEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group scores a point.\n *\n * @author Daniel Saukel\n */\npublic class GroupScoreEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GamePlayer scorer;\n    private PlayerGroup loserGroup;\n\n    public GroupScoreEvent(PlayerGroup group, GamePlayer scorer, PlayerGroup loserGroup) {\n        super(group);\n        this.scorer = scorer;\n        this.loserGroup = loserGroup;\n    }\n\n    /**\n     * Returns the player who scored.\n     *\n     * @return the player who scored\n     */\n    public GamePlayer getScorer() {\n        return scorer;\n    }\n\n    /**\n     * Returns the group that lost a score to the scorers.\n     *\n     * @return the group that lost a score to the scorers\n     */\n    public PlayerGroup getLoserGroup() {\n        return loserGroup;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; scorer=\" + scorer + \"; loserGroup=\" + loserGroup + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/group/GroupStartFloorEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.group;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a group starts playing a floor.\n *\n * @author Daniel Saukel\n */\npublic class GroupStartFloorEvent extends GroupEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private GameWorld gameWorld;\n\n    public GroupStartFloorEvent(PlayerGroup group, GameWorld gameWorld) {\n        super(group);\n        this.gameWorld = gameWorld;\n    }\n\n    /**\n     * Returns the game instance.\n     *\n     * @return the game instance\n     */\n    public GameWorld getGameWorld() {\n        return gameWorld;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{group=\" + group + \"; gameWorld=\" + gameWorld + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/mob/DungeonMobDeathEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.mob;\n\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link DungeonMob} dies.\n *\n * @author Daniel Saukel\n */\npublic class DungeonMobDeathEvent extends DungeonMobEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    public DungeonMobDeathEvent(DungeonMob mob) {\n        super(mob);\n    }\n\n    /**\n     * Returns the player who killed the mob or null if the cause of its death was not a player.\n     *\n     * @return the player who killed the mob or null if the cause of its death was not a player\n     */\n    public Player getKiller() {\n        if (mob.getEntity() == null) {\n            return null;\n        }\n        return mob.getEntity().getKiller();\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{mob=\" + mob + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/mob/DungeonMobEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.mob;\n\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving DungeonsXL mobs.\n *\n * @author Daniel Saukel\n */\npublic abstract class DungeonMobEvent extends Event {\n\n    protected DungeonMob mob;\n\n    protected DungeonMobEvent(DungeonMob mob) {\n        this.mob = mob;\n    }\n\n    /**\n     * Returns the DungeonMob involved in this event.\n     *\n     * @return the DungeonMob involved in this event\n     */\n    public DungeonMob getDungeonMob() {\n        return mob;\n    }\n\n    /**\n     * Returns the Bukkit LivingEntity involved in this event.\n     *\n     * @return the Bukkit LivingEntity involved in this event\n     */\n    public LivingEntity getBukkitEntity() {\n        return mob.getEntity();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/mob/DungeonMobSpawnEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.mob;\n\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a spawned entity is registered as a {@link DungeonMob}.\n * <p>\n * Use {@link org.bukkit.event.entity.CreatureSpawnEvent} if you need to prevent a mob from spawning.\n *\n * @author Daniel Saukel\n */\npublic class DungeonMobSpawnEvent extends DungeonMobEvent {\n\n    private static final HandlerList handlers = new HandlerList();\n\n    public DungeonMobSpawnEvent(DungeonMob mob) {\n        super(mob);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{mob=\" + mob + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/EditPlayerEditEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player starts editing a dungeon map.\n *\n * @author Daniel Saukel\n */\npublic class EditPlayerEditEvent extends EditPlayerEvent {\n\n    private static final HandlerList handlers = new HandlerList();\n\n    private boolean newlyLoaded;\n\n    public EditPlayerEditEvent(EditPlayer editPlayer, boolean newlyLoaded) {\n        super(editPlayer);\n        this.newlyLoaded = newlyLoaded;\n    }\n\n    /**\n     * Returns true if the edit world was not instantiated before the player edited it and false if it was.\n     *\n     * @return true if the edit world was not instantiated before the player edited it and false if it was\n     */\n    public boolean isNewlyLoaded() {\n        return newlyLoaded;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + globalPlayer + \"; newlyLoaded=\" + newlyLoaded + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/EditPlayerEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\n\n/**\n * Superclass for events involving {@link EditPlayer}s.\n *\n * @author Daniel Saukel\n */\npublic abstract class EditPlayerEvent extends GlobalPlayerEvent {\n\n    protected EditPlayerEvent(EditPlayer editPlayer) {\n        super(editPlayer);\n    }\n\n    /**\n     * Returns the EditPlayer involved in this event.\n     *\n     * @return the EditPlayer involved in this event\n     */\n    public EditPlayer getEditPlayer() {\n        return (EditPlayer) globalPlayer;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/EditPlayerLeaveEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player stops editing a dungeon map.\n *\n * @author Daniel Saukel\n */\npublic class EditPlayerLeaveEvent extends EditPlayerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private boolean escape;\n    private boolean unloadIfEmpty;\n\n    public EditPlayerLeaveEvent(EditPlayer editPlayer, boolean escape, boolean unloadIfEmpty) {\n        super(editPlayer);\n        this.escape = escape;\n        this.unloadIfEmpty = unloadIfEmpty;\n    }\n\n    /**\n     * Returns false if the edit world is saved, true if not.\n     *\n     * @return false if the edit world is saved, true if not\n     */\n    public boolean isEscape() {\n        return escape;\n    }\n\n    /**\n     * Returns if the instance shall be unloaded when it is empty after the player left.\n     *\n     * @return if the instance shall be unloaded when it is empty after the player left\n     */\n    public boolean getUnloadIfEmpty() {\n        return unloadIfEmpty;\n    }\n\n    /**\n     * Sets if the instance shall be unloaded when it is empty after the player left.\n     *\n     * @param unloadIfEmpty if the instance shall be unloaded when it is empty after the player left\n     */\n    public void setUnloadIfEmpty(boolean unloadIfEmpty) {\n        this.unloadIfEmpty = unloadIfEmpty;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + globalPlayer + \"; escape=\" + escape + \"; unloadIfEmpty=\" + unloadIfEmpty + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/GamePlayerDeathEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player dies in a dungeon. This is also fired when a player does not technically die because the deathScreen rule prevented the death.\n *\n * @author Daniel Saukel\n */\npublic class GamePlayerDeathEvent extends GamePlayerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private boolean keepInventory;\n    private int lostLives;\n\n    public GamePlayerDeathEvent(GamePlayer gamePlayer, boolean keepInventory, int lostLives) {\n        super(gamePlayer);\n        this.lostLives = lostLives;\n        this.keepInventory = keepInventory;\n    }\n\n    /**\n     * If the player's state - including his inventory, EXP etc. - is kept.\n     *\n     * @return if the player's state is kept\n     */\n    public boolean isInventoryKept() {\n        return keepInventory;\n    }\n\n    /**\n     * Sets if the player's state - including his inventory, EXP etc. - is kept.\n     *\n     * @param keepInventory if the player's state is kept\n     */\n    public void setInventoryKept(boolean keepInventory) {\n        this.keepInventory = keepInventory;\n    }\n\n    /**\n     * Returns the amount of lives the player loses.\n     *\n     * @return the amount of lives the player loses\n     */\n    public int getLostLives() {\n        return lostLives;\n    }\n\n    /**\n     * Sets the amount of lives the player loses.\n     *\n     * @param lostLives the lives the player loses\n     */\n    public void setLostLives(int lostLives) {\n        this.lostLives = lostLives;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + globalPlayer + \"; keepInventory=\" + keepInventory + \"; lostLives=\" + lostLives + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/GamePlayerEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\n\n/**\n * Superclass for events involving {@link GamePlayer}s.\n *\n * @author Daniel Saukel\n */\npublic abstract class GamePlayerEvent extends GlobalPlayerEvent {\n\n    protected GamePlayerEvent(GamePlayer gamePlayer) {\n        super(gamePlayer);\n    }\n\n    /**\n     * Returns the GamePlayer involved in this event.\n     *\n     * @return the GamePlayer involved in this event\n     */\n    public GamePlayer getGamePlayer() {\n        return (GamePlayer) globalPlayer;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/GamePlayerFinishEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player finishs a game.\n * <p>\n * Do not confuse this with {@link de.erethon.dungeonsxl.api.event.group.GroupFinishDungeonEvent}. GamePlayerFinishEvent is fired when a player triggers an end\n * sign, while GroupFinishDungeonEvent is triggered when all group members have triggered the ready sign and the game actually ends.\n *\n * @author Daniel Saukel\n */\npublic class GamePlayerFinishEvent extends GamePlayerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private boolean hasToWait;\n\n    public GamePlayerFinishEvent(GamePlayer gamePlayer, boolean hasToWait) {\n        super(gamePlayer);\n        this.hasToWait = hasToWait;\n    }\n\n    /**\n     * Returns false if the other group members have all already triggered the end sign, true if not.\n     *\n     * @return false if the other group members have all already triggered the end sign, true if not\n     */\n    public boolean getHasToWait() {\n        return hasToWait;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + globalPlayer + \"; hasToWait=\" + hasToWait + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/GlobalPlayerEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving DungeonsXL players.\n *\n * @author Daniel Saukel\n */\npublic abstract class GlobalPlayerEvent extends Event {\n\n    protected GlobalPlayer globalPlayer;\n\n    protected GlobalPlayerEvent(GlobalPlayer globalPlayer) {\n        this.globalPlayer = globalPlayer;\n    }\n\n    /**\n     * Returns the GlobalPlayer involved in this event\n     *\n     * @return the GlobalPlayer involved in this event\n     */\n    public GlobalPlayer getGlobalPlayer() {\n        return globalPlayer;\n    }\n\n    /**\n     * Returns the Bukkit Player involved in this event\n     *\n     * @return the Bukkit Player involved in this event\n     */\n    public Player getBukkitPlayer() {\n        if (globalPlayer == null) {\n            return null;\n        }\n        return globalPlayer.getPlayer();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/player/GlobalPlayerRewardPayOutEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.player;\n\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport java.util.List;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a player gets his {@link Reward}s after finishing a game.\n *\n * @see GlobalPlayer#setRewardItems(java.util.List)\n * @author Daniel Saukel\n */\npublic class GlobalPlayerRewardPayOutEvent extends GlobalPlayerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private final List<Reward> rewards;\n\n    public GlobalPlayerRewardPayOutEvent(GlobalPlayer globalPlayer, List<Reward> rewards) {\n        super(globalPlayer);\n        this.rewards = rewards;\n    }\n\n    /**\n     * Returns a list of the rewards the player will get.\n     *\n     * @return a list of the rewards the player will get\n     */\n    public List<Reward> getRewards() {\n        return rewards;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + globalPlayer + \"; rewards=\" + rewards + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/requirement/RequirementCheckEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.requirement;\n\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when it is checked if a player fulfills a {@link Requirement} realized through the Requirement API.\n * <p>\n * Note that this is usually called twice per player: When he tries to enter a dungeon and when he tries to start a game.\n *\n * @author Daniel Saukel\n */\npublic class RequirementCheckEvent extends RequirementEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private Player player;\n    private boolean keepInventory;\n    private BaseComponent[] checkMessage;\n\n    public RequirementCheckEvent(Requirement requirement, Dungeon dungeon, Player player, boolean keepInventory) {\n        super(requirement, dungeon);\n        this.player = player;\n        this.keepInventory = keepInventory;\n        checkMessage = requirement.getCheckMessage(player);\n    }\n\n    /**\n     * Returns the checked player.\n     *\n     * @return the player\n     */\n    public Player getPlayer() {\n        return player;\n    }\n\n    /**\n     * Sets the checked player.\n     *\n     * @param player the player\n     */\n    public void setPlayer(Player player) {\n        this.player = player;\n    }\n\n    /**\n     * Returns the message that will be sent to the player to inform him what he needs in order to fulfill the requirement if there is a requirement that he\n     * does not fulfill.\n     *\n     * @return the message that will be sent to the player to inform him what he needs in order to fulfill the requirement if there is a requirement that he\n     *         does not fulfill\n     */\n    public BaseComponent[] getCheckMessage() {\n        return checkMessage;\n    }\n\n    /**\n     * Sets the message that will be sent to the player to inform him what he needs in order to fulfill the requirement if there is a a requirement that he does\n     * not fulfill.\n     *\n     * @param checkMessage the message component array\n     */\n    public void setCheckMessage(BaseComponent[] checkMessage) {\n        this.checkMessage = checkMessage;\n    }\n\n    /**\n     * If the player's state - including his inventory, EXP etc. - is kept.\n     *\n     * @return if the player's state is kept\n     */\n    public boolean isInventoryKept() {\n        return keepInventory;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{requirement=\" + requirement + \"; player=\" + player + \"; keepInventory=\" + keepInventory + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/requirement/RequirementDemandEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.requirement;\n\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link Requirement} is demanded from a player.\n * <p>\n * This is fired for all requirements, even for those that do not demand anything from the player.\n *\n * @author Daniel Saukel\n */\npublic class RequirementDemandEvent extends RequirementEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private Player player;\n    private boolean keepInventory;\n\n    public RequirementDemandEvent(Requirement requirement, Dungeon dungeon, Player player, boolean keepInventory) {\n        super(requirement, dungeon);\n        this.player = player;\n        this.keepInventory = keepInventory;\n    }\n\n    /**\n     * Returns the player who pays the requirement.\n     *\n     * @return the player\n     */\n    public Player getPlayer() {\n        return player;\n    }\n\n    /**\n     * Sets the player who pays the requirement.\n     *\n     * @param player the player\n     */\n    public void setPlayer(Player player) {\n        this.player = player;\n    }\n\n    /**\n     * If the player's state - including his inventory, EXP etc. - is kept.\n     *\n     * @return if the player's state is kept\n     */\n    public boolean isInventoryKept() {\n        return keepInventory;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{requirement=\" + requirement + \"; player=\" + player + \"; keepInventory=\" + keepInventory + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/requirement/RequirementEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.requirement;\n\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving {@link Requirement}s.\n *\n * @author Daniel Saukel\n */\npublic abstract class RequirementEvent extends Event {\n\n    protected Requirement requirement;\n    protected Dungeon dungeon;\n\n    public RequirementEvent(Requirement requirement, Dungeon dungeon) {\n        this.requirement = requirement;\n        this.dungeon = dungeon;\n    }\n\n    /**\n     * Returns the dungeon involved in this event.\n     *\n     * @return the dungeon involved in this event\n     */\n    public Dungeon getDungeon() {\n        return dungeon;\n    }\n\n    /**\n     * Returns the requirement involved in this event.\n     *\n     * @return the requirement involved in this event\n     */\n    public Requirement getRequirement() {\n        return requirement;\n    }\n\n    /**\n     * Sets the requirement involved in this event.\n     *\n     * @param requirement the requirement\n     */\n    public void setRequirement(Requirement requirement) {\n        this.requirement = requirement;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/trigger/TriggerActionEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.trigger;\n\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport java.util.List;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link Trigger} is satisfied.\n *\n * @author Daniel Saukel\n */\npublic class TriggerActionEvent extends TriggerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private List<TriggerListener> fired;\n\n    public TriggerActionEvent(Trigger trigger, List<TriggerListener> fired) {\n        super(trigger);\n        this.fired = fired;\n    }\n\n    /**\n     * Returns a List of the fired listeners, e.g. the dungeon signs that are triggered because all their trigger expression is fully satisfied.\n     *\n     * @return a List of the fired listeners\n     */\n    public List<TriggerListener> getFiredListeners() {\n        return fired;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{trigger=\" + trigger + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/trigger/TriggerEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.trigger;\n\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving triggers.\n *\n * @author Daniel Saukel\n */\npublic abstract class TriggerEvent extends Event {\n\n    protected Trigger trigger;\n\n    protected TriggerEvent(Trigger trigger) {\n        this.trigger = trigger;\n    }\n\n    /**\n     * Returns the Trigger involved in this event.\n     *\n     * @return the trigger involved in this event\n     */\n    public Trigger getTrigger() {\n        return trigger;\n    }\n\n    /**\n     * Sets the trigger involved in this event to the given value.\n     *\n     * @param trigger the trigger to set\n     */\n    public void setTrigger(Trigger trigger) {\n        this.trigger = trigger;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/trigger/TriggerRegistrationEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.trigger;\n\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link Trigger} is created.\n *\n * @author Daniel Saukel\n */\npublic class TriggerRegistrationEvent extends TriggerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    public TriggerRegistrationEvent(Trigger trigger) {\n        super(trigger);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{trigger=\" + trigger + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/trigger/TriggerUnregistrationEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.trigger;\n\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link Trigger} is unregistered.\n *\n * @author Daniel Saukel\n */\npublic class TriggerUnregistrationEvent extends TriggerEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    public TriggerUnregistrationEvent(Trigger trigger) {\n        super(trigger);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{trigger=\" + trigger + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/EditWorldEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.EditWorld;\n\n/**\n * Superclass for events involving DungeonsXL edit instances.\n *\n * @author Daniel Saukel\n */\npublic abstract class EditWorldEvent extends InstanceWorldEvent {\n\n    protected EditWorldEvent(EditWorld editWorld) {\n        super(editWorld);\n    }\n\n    /**\n     * Returns the EditWorld involved in this event.\n     *\n     * @return the EditWorld involved in this event\n     */\n    public EditWorld getEditWorld() {\n        return (EditWorld) instance;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/EditWorldGenerateEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired after a dungeon world is generated.\n *\n * @author Daniel Saukel\n */\npublic class EditWorldGenerateEvent extends EditWorldEvent {\n\n    private static final HandlerList handlers = new HandlerList();\n\n    public EditWorldGenerateEvent(EditWorld editWorld) {\n        super(editWorld);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{instance=\" + instance + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/EditWorldSaveEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when an edit world is saved.\n *\n * @author Daniel Saukel\n */\npublic class EditWorldSaveEvent extends EditWorldEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    public EditWorldSaveEvent(EditWorld editWorld) {\n        super(editWorld);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{instance=\" + instance + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/EditWorldUnloadEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when an edit world is unloaded.\n *\n * @author Daniel Saukel\n */\npublic class EditWorldUnloadEvent extends InstanceWorldUnloadEvent {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private boolean save;\n\n    public EditWorldUnloadEvent(EditWorld editWorld, boolean save) {\n        super(editWorld);\n        this.save = save;\n    }\n\n    /**\n     * Returns if the world is saved.\n     *\n     * @return if the world is saved\n     */\n    public boolean getSave() {\n        return save;\n    }\n\n    /**\n     * Sets if the world shall be saved.\n     *\n     * @param save if the world shall be saved\n     */\n    public void setSave(boolean save) {\n        this.save = save;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{instance=\" + instance + \"; save=\" + save + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/GameWorldEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\n\n/**\n * Superclass for events involving DungeonsXL game instances.\n *\n * @author Daniel Saukel\n */\npublic abstract class GameWorldEvent extends InstanceWorldEvent {\n\n    private Dungeon dungeon;\n\n    protected GameWorldEvent(GameWorld gameWorld, Dungeon dungeon) {\n        super(gameWorld);\n        this.dungeon = dungeon;\n    }\n\n    /**\n     * Returns the GameWorld involved in this event.\n     *\n     * @return the GameWorld involved in this event.\n     */\n    public GameWorld getGameWorld() {\n        return (GameWorld) instance;\n    }\n\n    /**\n     * Returns the dungeon the game instance is a part of.\n     *\n     * @return the dungeon the game instance is a part of\n     */\n    public Dungeon getDungeon() {\n        return dungeon;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/GameWorldStartGameEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when the game starts, that means when all players have triggered the ready sign.\n *\n * @author Daniel Saukel\n */\npublic class GameWorldStartGameEvent extends GameWorldEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private Game game;\n\n    public GameWorldStartGameEvent(GameWorld gameWorld, Game game) {\n        super(gameWorld, gameWorld.getDungeon());\n        this.game = game;\n    }\n\n    /**\n     * Returns the game.\n     *\n     * @return the game\n     */\n    public Game getGame() {\n        return game;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{instance=\" + instance + \"; dungeon=\" + getDungeon() + \"; game=\" + game + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/InstanceWorldEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.World;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving DungeonsXL instances.\n *\n * @author Daniel Saukel\n */\npublic abstract class InstanceWorldEvent extends Event {\n\n    protected InstanceWorld instance;\n\n    protected InstanceWorldEvent(InstanceWorld instance) {\n        this.instance = instance;\n    }\n\n    /**\n     * Returns the instance involved in this event.\n     *\n     * @return the instance involved in this event\n     */\n    public InstanceWorld getInstance() {\n        return instance;\n    }\n\n    /**\n     * Returns the Bukkit world involved in this event.\n     *\n     * @return the Bukkit world involved in this event.\n     */\n    public World getBukkitWorld() {\n        return instance.getWorld();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/InstanceWorldPostUnloadEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired after an instance world is unloaded.\n *\n * @author Daniel Saukel\n */\npublic class InstanceWorldPostUnloadEvent extends ResourceWorldEvent {\n\n    private static final HandlerList handlers = new HandlerList();\n    private String instanceWorldName;\n\n    public InstanceWorldPostUnloadEvent(ResourceWorld resource, String instanceWorldName) {\n        super(resource);\n        this.instanceWorldName = instanceWorldName;\n    }\n\n    /**\n     * Returns the name the instance world had.\n     *\n     * @return the name the instance world had\n     */\n    public String getInstanceWorldName() {\n        return instanceWorldName;\n    }\n\n    /**\n     * Returns if the unloaded instance was an edit world.\n     *\n     * @return if the unloaded instance was an edit world\n     */\n    public boolean wasEditInstance() {\n        return instanceWorldName.startsWith(\"DXL_Edit_\");\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{resource=\" + resource + \"; instanceWorldName=\" + instanceWorldName + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/InstanceWorldUnloadEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when an instance is unloaded.\n *\n * @author Daniel Saukel\n */\npublic class InstanceWorldUnloadEvent extends InstanceWorldEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    public InstanceWorldUnloadEvent(InstanceWorld instance) {\n        super(instance);\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{instance=\" + instance + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/ResourceWorldEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport org.bukkit.event.Event;\n\n/**\n * Superclass for events involving DungeonsXL resource worlds.\n *\n * @author Daniel Saukel\n */\npublic abstract class ResourceWorldEvent extends Event {\n\n    protected ResourceWorld resource;\n\n    protected ResourceWorldEvent(ResourceWorld resource) {\n        this.resource = resource;\n    }\n\n    /**\n     * Returns the resource world involved in this event.\n     *\n     * @return the resource world involved in this event.\n     */\n    public ResourceWorld getResource() {\n        return resource;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/event/world/ResourceWorldInstantiateEvent.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.event.world;\n\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.HandlerList;\n\n/**\n * Fired when a {@link ResourceWorld} is instantiated.\n *\n * @author Daniel Saukel\n */\npublic class ResourceWorldInstantiateEvent extends ResourceWorldEvent implements Cancellable {\n\n    private static final HandlerList handlers = new HandlerList();\n    private boolean cancelled;\n\n    private String instanceWorldName;\n\n    public ResourceWorldInstantiateEvent(ResourceWorld resource, String instanceWorldName) {\n        super(resource);\n        this.instanceWorldName = instanceWorldName;\n    }\n\n    /**\n     * Returns if the loaded instance will be an edit world.\n     *\n     * @return if the loaded instance will be an edit world\n     */\n    public boolean isEditInstance() {\n        return instanceWorldName.startsWith(\"DXL_Edit_\");\n    }\n\n    /**\n     * Returns the name the newly loaded Bukkit world is going to have.\n     * <p>\n     * Note that at this point no Bukkit World object for this world exists.\n     *\n     * @return the name the newly loaded Bukkit world is going to have\n     */\n    public String getInstanceWorldName() {\n        return instanceWorldName;\n    }\n\n    @Override\n    public HandlerList getHandlers() {\n        return handlers;\n    }\n\n    public static HandlerList getHandlerList() {\n        return handlers;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public void setCancelled(boolean cancelled) {\n        this.cancelled = cancelled;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{resource=\" + resource + \"; instanceWorldName=\" + instanceWorldName + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/mob/DungeonMob.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.mob;\n\nimport de.erethon.xlib.mob.ExMob;\nimport org.bukkit.entity.LivingEntity;\n\n/**\n * Wrapper for a mob spawned in a dungeon.\n *\n * @author Daniel Saukel\n */\npublic interface DungeonMob {\n\n    /**\n     * Returns the entity that is wrapped by this object.\n     *\n     * @return the entity that is wrapped by this object\n     */\n    LivingEntity getEntity();\n\n    /**\n     * Returns the XLib representation of the mob or null if it is spawned by an external plugin.\n     *\n     * @return the XLib representation of the mob or null if it is spawned by an external plugin\n     */\n    ExMob getType();\n\n    /**\n     * Returns the String used to identify this mob for example in the context of triggers.\n     *\n     * @return the String used to identify this mob for example in the context of triggers\n     */\n    String getTriggerId();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/mob/ExternalMobProvider.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.mob;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\n\n/**\n * Other plugins / libraries that can handle and spawn mobs.\n *\n * @author Daniel Saukel\n */\npublic interface ExternalMobProvider {\n\n    /**\n     * Returns the identifier used on mob signs to spawn mobs from this provider.\n     *\n     * @return the identifier used on mob signs to spawn mobs from this provider\n     */\n    String getIdentifier();\n\n    /**\n     * Returns the raw console spawn command of the provider.\n     * <p>\n     * This method is necessary for the default implementation of {@link #getCommand(String, String, double, double, double)}.\n     *\n     * @return the raw console spawn command of the provider\n     */\n    String getRawCommand();\n\n    /**\n     * Returns the console spawn command of the provider with values replaced to spawn the mob represented by the given String.\n     * <p>\n     * The default implementation uses %mob%, %world%, %x%, %y% and %z% as placeholders and alternatively %block_x% etc. if values without decimals are needed.\n     * <p>\n     * This method is used in the default implementation of {@link #summon(String, org.bukkit.Location)}.\n     *\n     * @param mob   the mob identifier\n     * @param world the game world\n     * @param x     the x coordinate\n     * @param y     the y coordinate\n     * @param z     the z coordinate\n     * @return the command with replaced variables\n     */\n    default String getCommand(String mob, String world, double x, double y, double z) {\n        return getRawCommand().replace(\"%mob%\", mob).replace(\"%world%\", world)\n                .replace(\"%x%\", String.valueOf(x)).replace(\"%y%\", String.valueOf(y)).replace(\"%z%\", String.valueOf(z))\n                .replace(\"%block_x%\", String.valueOf(Location.locToBlock(x)))\n                .replace(\"%block_y%\", String.valueOf(Location.locToBlock(y)))\n                .replace(\"%block_z%\", String.valueOf(Location.locToBlock(z)));\n    }\n\n    /**\n     * Summons the mob.\n     * <p>\n     * The default implementation requires {@link #getCommand(String, String, double, double, double)} to be implemented.\n     *\n     * @param mob      the mob identifier\n     * @param location the location where the mob will be spawned\n     */\n    default void summon(String mob, Location location) {\n        Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), getCommand(mob, location.getWorld().getName(), location.getX(), location.getY(), location.getZ()));\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/mob/MobSet.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.mob;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.entity.LivingEntity;\n\n/**\n * Mobs spawned through a mob sign are added to mob sets. This allows for all mobs in a set to be referred to together with the ID of the mob.\n *\n * @author Daniel Saukel\n */\npublic class MobSet {\n\n    private String id;\n    private List<LivingEntity> spawned;\n    private int size;\n    private int reserved;\n    private int killed;\n\n    public MobSet(String id) {\n        this.id = id;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public int getSize() {\n        return size;\n    }\n\n    public int getReserved() {\n        return reserved;\n    }\n\n    public void allocate(int amount) {\n        size += amount;\n        reserved += amount;\n    }\n\n    public void initialize() {\n        spawned = new ArrayList<>(size);\n    }\n\n    public int getKilled() {\n        return killed;\n    }\n\n    public void spawn(LivingEntity entity) {\n        spawned.add(entity);\n    }\n\n    public void kill(LivingEntity entity) {\n        spawned.remove(entity);\n    }\n\n    public boolean checkTrigger(int amount) {\n        return killed >= amount;\n    }\n\n    public boolean checkTrigger(double quota) {\n        return killed / size >= quota;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/EditPlayer.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.world.EditWorld;\n\n/**\n * Represents a player in an edit instance.\n * <p>\n * All players in an edit world have one wrapper object that is an instance of EditPlayer.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: poke\npublic interface EditPlayer extends InstancePlayer {\n\n    /**\n     * Returns the {@link de.erethon.dungeonsxl.api.world.EditWorld} the player is editing.\n     *\n     * @return the {@link de.erethon.dungeonsxl.api.world.EditWorld} the player is editing\n     */\n    EditWorld getEditWorld();\n\n    /**\n     * Returns the lines of a sign the player has copied with a stick tool in an array with the length of four.\n     *\n     * @return the lines of a sign the player has copied with a stick tool in an array with the length of four\n     */\n    String[] getCopiedLines();\n\n    /**\n     * Sets the memorized sign lines.\n     *\n     * @param copiedLines the lines\n     */\n    void setCopiedLines(String[] copiedLines);\n\n    /**\n     * Makes the player leave his group and dungeon.\n     * <p>\n     * This unloads the world if there are no editors left after this player leaves.\n     */\n    @Override\n    default void leave() {\n        leave(true);\n    }\n\n    /**\n     * Makes the player leave his group and dungeon.\n     *\n     * @param unloadIfEmpty whether the world is to be unloaded if, after this player leaves, no editors are left\n     */\n    void leave(boolean unloadIfEmpty);\n\n    /**\n     * Makes the player leave the edit world without saving the progress.\n     */\n    void escape();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/GamePlayer.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Wolf;\n\n/**\n * Represents a player in a game dungeon instance.\n * <p>\n * All players in a game world have one wrapper object that is an instance of GamePlayer.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: isInTestMode, setReady, [wolf, group tag methods], finishFloor\npublic interface GamePlayer extends InstancePlayer {\n\n    /**\n     * Returns the game the player's group plays.\n     *\n     * @return the game the player's group plays\n     */\n    default Game getGame() {\n        return getGroup().getGame();\n    }\n\n    /**\n     * Returns the game world of the player's group.\n     *\n     * @return the game world of the player's group\n     */\n    default GameWorld getGameWorld() {\n        return getGroup().getGameWorld();\n    }\n\n    /**\n     * Returns if the player is ready to start the game.\n     * <p>\n     * This is usually achieved by triggering a ready sign.\n     *\n     * @return if the player is ready to start the game\n     */\n    boolean isReady();\n\n    /**\n     * Returns if the player finished the game.\n     * <p>\n     * This is usually achieved by triggering an end sign.\n     * <p>\n     * It is used for both the end of a whole dungeon and the end of a floor.\n     *\n     * @return if the player finished the game\n     */\n    boolean isFinished();\n\n    /**\n     * Sets if the player finished their game.\n     *\n     * @param finished if the player finished the game\n     */\n    void setFinished(boolean finished);\n\n    /**\n     * Returns the player's class or null if they have none.\n     *\n     * @return the player's class\n     */\n    PlayerClass getPlayerClass();\n\n    /**\n     * Sets and applies the given class.\n     *\n     * @param playerClass the class\n     */\n    void setPlayerClass(PlayerClass playerClass);\n\n    /**\n     * Returns the location of the last checkpoint the player reached.\n     *\n     * @return the location of the last checkpoint the player reached\n     */\n    Location getLastCheckpoint();\n\n    /**\n     * Sets the location of the last checkpoint the player reached.\n     * <p>\n     * This is where the player respawns if they die and have -1 or &gt;0 {@link #getLives() lives} left.\n     *\n     * @param checkpoint the checkpoint location\n     */\n    void setLastCheckpoint(Location checkpoint);\n\n    /**\n     * Returns the saved time millis from when the player went offline.\n     *\n     * @return the saved time millis from when the player went offline\n     */\n    long getOfflineTimeMillis();\n\n    /**\n     * Sets the saved time millis from when the player went offline.\n     *\n     * @param time the time millis\n     */\n    void setOfflineTimeMillis(long time);\n\n    /**\n     * Returns the original amount of lives the player had in the current game or -1 if lives aren't used.\n     *\n     * @return the original amount of lives the player had in the current game or -1 if lives aren't used\n     */\n    int getInitialLives();\n\n    /**\n     * Sets the original amount of lives the player had in the current game; -1 means lives aren't used.\n     *\n     * @param lives the amount of lives\n     */\n    void setInitialLives(int lives);\n\n    /**\n     * Returns the lives the player has left or -1 if per player lives aren't used.\n     *\n     * @return the lives the player has left or -1 if per player lives aren't used\n     */\n    int getLives();\n\n    /**\n     * Sets the lives the player has left.\n     * <p>\n     * This is not to be used if the dungeon uses group lives.\n     *\n     * @param lives the lives\n     */\n    void setLives(int lives);\n\n    /**\n     * Returns the player's wolf or null if he does not have one.\n     *\n     * @return the player's wolf or null if he does not have one\n     * @deprecated More dynamic pet features might make this obsolete in the future.\n     */\n    @Deprecated\n    Wolf getWolf();\n\n    /**\n     * Gives the player a wolf.\n     *\n     * @param wolf the wolf\n     * @deprecated More dynamic pet features might make this obsolete in the future.\n     */\n    @Deprecated\n    void setWolf(Wolf wolf);\n\n    /**\n     * Returns if the player is stealing another group's flag.\n     *\n     * @return if the player is stealing another group's flag\n     */\n    boolean isStealingFlag();\n\n    /**\n     * Returns the group whose flag the player robbed; null if the player isn't stealing any.\n     *\n     * @return the group whose flag the player robbed; null if the player isn't stealing any\n     */\n    PlayerGroup getRobbedGroup();\n\n    /**\n     * Sets the player to be stealing the team flag of the given group.\n     *\n     * @param group the group\n     */\n    void setRobbedGroup(PlayerGroup group);\n\n    /**\n     * Scores a point.\n     */\n    void captureFlag();\n\n    /**\n     * Makes the player leave his group and dungeon.\n     * <p>\n     * This sends default messages to the player.\n     */\n    @Override\n    default void leave() {\n        leave(true);\n    }\n\n    /**\n     * Makes the player leave his group and dungeon.\n     *\n     * @param sendMessages if default messages shall be sent to the player\n     */\n    void leave(boolean sendMessages);\n\n    /**\n     * Treats the player as if they lost their last life and kicks them from the dungeon.\n     */\n    void kill();\n\n    /**\n     * Sets the player to be ready to start the dungeon game, like when a ready sign is triggered.\n     * <p>\n     * If all other players in the group are already {@link #isReady() ready}, the game is started.\n     *\n     * @return if the game has been started.\n     */\n    boolean ready();\n\n    /**\n     * Respawns the player. Also teleports DXL pets if there are any.\n     */\n    void respawn();\n\n    /**\n     * The player finishs the current game.\n     * <p>\n     * This sends default messages to the player.\n     */\n    default void finish() {\n        finish(true);\n    }\n\n    /**\n     * The player finishs the current game.\n     *\n     * @param sendMessages if default messages shall be sent to the player\n     */\n    void finish(boolean sendMessages);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/GlobalPlayer.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.player.PlayerWrapper;\nimport java.util.List;\nimport org.bukkit.Location;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * Represents a player anywhere on the server.\n * <p>\n * All players on the server, including the ones in dungeons, have one wrapper object that is an instance of GlobalPlayer.\n * <p>\n * Do not cache this for the whole runtime (or use {@link de.erethon.xlib.player.PlayerCollection}). The object may be deleted and replaced with an object of\n * the appropriate type when the player enters or leaves an instance.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: getters and setters: data, portal, cached item, announcer; startTutorial\npublic interface GlobalPlayer extends PlayerWrapper {\n\n    /**\n     * Returns the player's group.\n     *\n     * @return the player's group.\n     */\n    PlayerGroup getGroup();\n\n    /**\n     * Returns if the player uses the built-in group chat.\n     *\n     * @return if the player uses the built-in group chat\n     */\n    boolean isInGroupChat();\n\n    /**\n     * Sets if the player uses the built-in group chat.\n     *\n     * @param groupChat if the player shall use the built-in group chat\n     */\n    void setInGroupChat(boolean groupChat);\n\n    /**\n     * Returns if the player may read messages from the built-in group chat.\n     *\n     * @return if the player may read messages from the built-in group chat\n     */\n    boolean isInChatSpyMode();\n\n    /**\n     * Sets if the player may read messages from the built-in group chat.\n     *\n     * @param chatSpyMode if the player may read messages from the built-in group chat\n     */\n    void setInChatSpyMode(boolean chatSpyMode);\n\n    /**\n     * Checks if the player has the given permission.\n     *\n     * @param permission the permission\n     * @return if the player has the given permission\n     */\n    boolean hasPermission(String permission);\n\n    /**\n     * Returns the reward items the player gets after leaving the dungeon.\n     *\n     * @return the reward items the player gets after leaving the dungeon\n     */\n    List<ItemStack> getRewardItems();\n\n    /**\n     * Sets the reward items the player gets after leaving the dungeon.\n     *\n     * @param rewardItems the reward items the player gets after leaving the dungeon\n     */\n    void setRewardItems(List<ItemStack> rewardItems);\n\n    /**\n     * Returns if the player has any reward items left.\n     *\n     * @return if the player has any reward items left\n     */\n    boolean hasRewardItemsLeft();\n\n    /**\n     * Returns if the player is currently breaking a global protection (=using /dxl break).\n     *\n     * @return if the player is currently breaking a global protection (=using /dxl break)\n     */\n    boolean isInBreakMode();\n\n    /**\n     * Sets the player into or out of break mode; see {@link #isInBreakMode()}.\n     *\n     * @param breakMode if the player may break global protections\n     */\n    void setInBreakMode(boolean breakMode);\n\n    /**\n     * Sends a message to the player.\n     * <p>\n     * Supports color codes.\n     *\n     * @param message the message to send\n     */\n    default void sendMessage(String message) {\n        MessageUtil.sendMessage(getPlayer(), message);\n    }\n\n    /**\n     * Respawns the player at the location defined by the game rules or his old position before he was in a dungeon.\n     *\n     * @param gameFinished if the game was finished\n     */\n    void reset(boolean gameFinished);\n\n    /**\n     * Respawns the player at the given location.\n     *\n     * @param tpLoc         the location where the player shall respawn\n     * @param keepInventory if the saved status shall be reset\n     */\n    void reset(Location tpLoc, boolean keepInventory);\n\n    /**\n     * Performs a requirement check for the given dungeon.\n     * <p>\n     * This method might send messages to the player to inform him that he does not fulfill them.\n     *\n     * @param dungeon the dungeon to check\n     * @return if the player fulfills the requirements or may bypass them\n     */\n    boolean checkRequirements(Dungeon dungeon);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/GroupAdapter.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.bukkit.entity.Player;\n\n/**\n * Implement and register in order to track a group.\n * <p>\n * See implementation classes in de.erethon.dungeonsxl.player.groupadapter for reference.\n *\n * @param <T> the external group object\n * @author Daniel Saukel\n */\npublic abstract class GroupAdapter<T> {\n\n    protected DungeonsAPI dxl;\n    protected Map<PlayerGroup, T> groups = new HashMap<>();\n\n    /**\n     * @param dxl the DungeonsAPI instance\n     */\n    protected GroupAdapter(DungeonsAPI dxl) {\n        this.dxl = dxl;\n    }\n\n    /**\n     * Creates a dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group.\n     *\n     * @param eGroup the external group\n     * @return a dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group\n     */\n    public abstract PlayerGroup createDungeonGroup(T eGroup);\n\n    /**\n     * Returns the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group or null of none exists.\n     *\n     * @param eGroup the external group\n     * @return the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group\n     */\n    public PlayerGroup getDungeonGroup(T eGroup) {\n        if (eGroup == null) {\n            return null;\n        }\n        for (Entry<PlayerGroup, T> entry : groups.entrySet()) {\n            if (entry.getValue().equals(eGroup)) {\n                return entry.getKey();\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group.\n     *\n     * @param dGroup the dungeon group\n     * @return the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group\n     */\n    public T getExternalGroup(PlayerGroup dGroup) {\n        return groups.get(dGroup);\n    }\n\n    /**\n     * Returns the dungeon group that mirrors the external group.\n     * <p>\n     * Creates a dungeon group if none exists and if the party has no more online members than maxSize.\n     *\n     * @param eGroup  the dungeon group\n     * @param maxSize the maximum size of the group\n     * @return the dungeon group that mirrors the dungeon group\n     */\n    public PlayerGroup getOrCreateDungeonGroup(T eGroup, int maxSize) {\n        if (eGroup == null) {\n            return null;\n        }\n        PlayerGroup dGroup = getDungeonGroup(eGroup);\n        if (dGroup == null && getGroupOnlineSize(eGroup) <= maxSize) {\n            dGroup = createDungeonGroup(eGroup);\n        }\n        return dGroup;\n    }\n\n    /**\n     * Returns the dungeon group that mirrors the external group.\n     * <p>\n     * Creates a dungeon group if none exists.\n     *\n     * @param eGroup the dungeon group\n     * @return the dungeon group that mirrors the dungeon group\n     */\n    public PlayerGroup getOrCreateDungeonGroup(T eGroup) {\n        if (eGroup == null) {\n            return null;\n        }\n        PlayerGroup dGroup = getDungeonGroup(eGroup);\n        if (dGroup == null) {\n            dGroup = createDungeonGroup(eGroup);\n        }\n        return dGroup;\n    }\n\n    /**\n     * Returns the external group of the given group member.\n     *\n     * @param member the group member\n     * @return the external group of the given group member\n     */\n    public abstract T getExternalGroup(Player member);\n\n    /**\n     * Returns the amount of members in the external group who are online.\n     *\n     * @param eGroup the external group\n     * @return the amount of members in the external group who are online\n     */\n    public abstract int getGroupOnlineSize(T eGroup);\n\n    /**\n     * Checks if two groups are corresponding.\n     * <p>\n     * Corresponding groups are groups that should be regarded as one from the perspective of a player.\n     * <p>\n     * Two null values are regarded as corresponding.\n     *\n     * @param dGroup the dungeon group\n     * @param eGroup the external group\n     * @return if the two groups are corresponding\n     */\n    public boolean areCorresponding(PlayerGroup dGroup, T eGroup) {\n        if (dGroup == null || eGroup == null) {\n            return false;\n        }\n        T dExternal = groups.get(dGroup);\n        return dExternal != null && eGroup.equals(dExternal);\n    }\n\n    /**\n     * Returns if the player is a member of any external group.\n     *\n     * @param player the player\n     * @return if the player is a member of any external group\n     */\n    public boolean isExternalGroupMember(Player player) {\n        return getExternalGroup(player) != null;\n    }\n\n    /**\n     * Returns if the player is a member of the external group.\n     *\n     * @param eGroup the external group\n     * @param player the player\n     * @return if the player is a member of the external group\n     */\n    public abstract boolean isExternalGroupMember(T eGroup, Player player);\n\n    /**\n     * Clears the external / dungeon group references.\n     */\n    public void clear() {\n        groups.clear();\n    }\n\n    /**\n     * Removes the external / dungeon group reference from the cache.\n     *\n     * @param dGroup the DXL group that belongs to an external group.\n     */\n    public void removeReference(PlayerGroup dGroup) {\n        groups.remove(dGroup);\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/InstancePlayer.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.World;\n\n/**\n * Represents a player in an instance.\n * <p>\n * All players in a world instantiated by DungeonsXL, have one wrapper object that is an instance of InstancePlayer.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: setWorld, clearPlayerData, delete, chat, update\npublic interface InstancePlayer extends GlobalPlayer {\n\n    /**\n     * The world of the instance, where the player is supposed to be.\n     *\n     * @return the world of the instance\n     */\n    World getWorld();\n\n    /**\n     * The world of the instance, where the player is supposed to be.\n     *\n     * @return the instance world\n     */\n    InstanceWorld getInstanceWorld();\n\n    /**\n     * Makes the player leave his group and dungeon.\n     */\n    void leave();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/PlayerCache.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.xlib.util.Registry;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.UUID;\nimport java.util.function.Predicate;\nimport org.bukkit.entity.Player;\n\n/**\n * Stores information on players in and out of dungeons.\n *\n * @author Daniel Saukel\n */\npublic class PlayerCache extends Registry<Player, GlobalPlayer> {\n\n    /**\n     * Returns the {@link GlobalPlayer} that represents the player with the given UUID.\n     *\n     * @param uuid a player's UUID to check\n     * @return the {@link GlobalPlayer} that represents the given player both for players in dungeons or outside; null if they are offline\n     */\n    public GlobalPlayer get(UUID uuid) {\n        return getFirstIf(p -> p.getUniqueId().equals(uuid));\n    }\n\n    /**\n     * Returns the {@link InstancePlayer} that represents the given player.\n     *\n     * @param player the player to check\n     * @return the {@link InstancePlayer} that represents the given player; null if the player is neither editing nor in a game.\n     */\n    public InstancePlayer getInstancePlayer(Player player) {\n        return getFirstInstancePlayerIf(p -> p.getPlayer() == player);\n    }\n\n    /**\n     * Returns the {@link EditPlayer} that represents the given player.\n     *\n     * @param player the player to check\n     * @return the {@link EditPlayer} that represents the given player; null if the player is not editing\n     */\n    public EditPlayer getEditPlayer(Player player) {\n        return getFirstEditPlayerIf(p -> p.getPlayer() == player);\n    }\n\n    /**\n     * Returns the {@link GamePlayer} that represents the given player.\n     *\n     * @param player the player to check\n     * @return the {@link GamePlayer} that represents the given player; null if the player is not in a game\n     */\n    public GamePlayer getGamePlayer(Player player) {\n        return getFirstGamePlayerIf(p -> p.getPlayer() == player);\n    }\n\n    /**\n     * Returns the first {@link InstancePlayer} that satisfies the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return the first {@link InstancePlayer} that satisfies the given predicate\n     */\n    public InstancePlayer getFirstInstancePlayerIf(Predicate<InstancePlayer> predicate) {\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof InstancePlayer)) {\n                continue;\n            }\n            InstancePlayer instancePlayer = (InstancePlayer) element;\n            if (predicate.test(instancePlayer)) {\n                return instancePlayer;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns the first {@link EditPlayer} that satisfies the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return the first {@link EditPlayer} that satisfies the given predicate\n     */\n    public EditPlayer getFirstEditPlayerIf(Predicate<EditPlayer> predicate) {\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof EditPlayer)) {\n                continue;\n            }\n            EditPlayer editPlayer = (EditPlayer) element;\n            if (predicate.test(editPlayer)) {\n                return editPlayer;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns the first {@link GamePlayer} that satisfies the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return the first {@link GamePlayer} that satisfies the given predicate\n     */\n    public GamePlayer getFirstGamePlayerIf(Predicate<GamePlayer> predicate) {\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof GamePlayer)) {\n                continue;\n            }\n            GamePlayer gamePlayer = (GamePlayer) element;\n            if (predicate.test(gamePlayer)) {\n                return gamePlayer;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns all {@link InstancePlayer}s that satisfy the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return all {@link InstancePlayer} that satisfy the given predicate\n     */\n    public Collection<InstancePlayer> getAllInstancePlayersIf(Predicate<InstancePlayer> predicate) {\n        Collection<InstancePlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof InstancePlayer)) {\n                continue;\n            }\n            InstancePlayer instancePlayer = (InstancePlayer) element;\n            if (predicate.test(instancePlayer)) {\n                checked.add(instancePlayer);\n            }\n        }\n        return checked;\n    }\n\n    /**\n     * Returns all {@link EditPlayer}s that satisfy the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return all {@link EditPlayer}s that satisfy the given predicate\n     */\n    public Collection<EditPlayer> getAllEditPlayersIf(Predicate<EditPlayer> predicate) {\n        Collection<EditPlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof EditPlayer)) {\n                continue;\n            }\n            EditPlayer editPlayer = (EditPlayer) element;\n            if (predicate.test(editPlayer)) {\n                checked.add(editPlayer);\n            }\n        }\n        return checked;\n    }\n\n    /**\n     * Returns all {@link GamePlayer}s that satisfy the given predicate.\n     *\n     * @param predicate the predicate to check\n     * @return all {@link GamePlayer} that satisfy the given predicate\n     */\n    public Collection<GamePlayer> getAllGamePlayersIf(Predicate<GamePlayer> predicate) {\n        Collection<GamePlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (!(element instanceof GamePlayer)) {\n                continue;\n            }\n            GamePlayer gamePlayer = (GamePlayer) element;\n            if (predicate.test(gamePlayer)) {\n                checked.add(gamePlayer);\n            }\n        }\n        return checked;\n    }\n\n    /**\n     * Returns all {@link InstancePlayer}s.\n     *\n     * @return all {@link InstancePlayer}s\n     */\n    public Collection<InstancePlayer> getAllInstancePlayers() {\n        Collection<InstancePlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (element instanceof InstancePlayer) {\n                checked.add((InstancePlayer) element);\n            }\n        }\n        return checked;\n    }\n\n    /**\n     * Returns all {@link EditPlayer}s.\n     *\n     * @return all {@link EditPlayer}s\n     */\n    public Collection<EditPlayer> getAllEditPlayers() {\n        Collection<EditPlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (element instanceof EditPlayer) {\n                checked.add((EditPlayer) element);\n            }\n        }\n        return checked;\n    }\n\n    /**\n     * Returns all {@link GamePlayer}s.\n     *\n     * @return all {@link GamePlayer}s\n     */\n    public Collection<GamePlayer> getAllGamePlayers() {\n        Collection<GamePlayer> checked = new ArrayList<>();\n        for (GlobalPlayer element : elements.values()) {\n            if (element instanceof GamePlayer) {\n                checked.add((GamePlayer) element);\n            }\n        }\n        return checked;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/PlayerClass.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.xlib.XLib;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.configuration.file.YamlConfiguration;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * Represents a class and a class script.\n *\n * @author Daniel Saukel\n */\npublic class PlayerClass {\n\n    private String name;\n\n    private List<ItemStack> items = new ArrayList<>();\n    private boolean dog;\n\n    /**\n     * Creates a PlayerClass from a class YAML file. The name is taken from the file name.\n     *\n     * @param xlib the XLib instance\n     * @param file     the class config file\n     */\n    public PlayerClass(XLib xlib, File file) {\n        this(xlib, file.getName().substring(0, file.getName().length() - 4), YamlConfiguration.loadConfiguration(file));\n    }\n\n    /**\n     * Creates a PlayerClass from the given class config.\n     *\n     * @param xlib the XLib instance\n     * @param name     the class name\n     * @param config   the config\n     */\n    public PlayerClass(XLib xlib, String name, FileConfiguration config) {\n        this.name = name;\n\n        if (config.contains(\"items\")) {\n            items = xlib.deserializeStackList(config, \"items\");\n        }\n\n        if (config.contains(\"dog\")) {\n            dog = config.getBoolean(\"dog\");\n        }\n    }\n\n    public PlayerClass(String name, List<ItemStack> items, boolean dog) {\n        this.items = items;\n        this.name = name;\n        this.dog = dog;\n    }\n\n    /**\n     * Returns the name of the class.\n     *\n     * @return the name of the class\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * Returns the list of the items this class gives to a player.\n     *\n     * @return the list of the the items this class gives to a player\n     */\n    public List<ItemStack> getItems() {\n        return items;\n    }\n\n    /**\n     * Adds the given item to this class.\n     *\n     * @param itemStack the ItemStack to add\n     */\n    public void addItem(ItemStack itemStack) {\n        items.add(itemStack);\n    }\n\n    /**\n     * Removes the given item from this class.\n     *\n     * @param itemStack the ItemStack to remove\n     */\n    public void removeItem(ItemStack itemStack) {\n        items.remove(itemStack);\n    }\n\n    /**\n     * Returns if the class gives the player a dog.\n     *\n     * @return if the class has a dog\n     * @deprecated More dynamic pet features might make this obsolete in the future.\n     */\n    @Deprecated\n    public boolean hasDog() {\n        return dog;\n    }\n\n    /**\n     * Sets if the class gives the player a dog.\n     *\n     * @param dog if the class shall give the player a dog\n     * @deprecated More dynamic pet features might make this obsolete in the future.\n     */\n    @Deprecated\n    public void setDog(boolean dog) {\n        this.dog = dog;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{name=\" + name + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/player/PlayerGroup.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.player;\n\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.player.PlayerCollection;\nimport java.util.List;\nimport org.bukkit.ChatColor;\nimport org.bukkit.DyeColor;\nimport org.bukkit.entity.Player;\n\n/**\n * Represents a group of players provided by DungeonsXL.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: setDungeon, setPlaying, [color, unplayed floor, floor count methods], isEmpty, isCustom, teleport,\n// finish, finishFloor, startGame, winGame, requirements methods\npublic interface PlayerGroup {\n\n    /**\n     * Links different color types together.\n     */\n    public enum Color {\n\n        BLACK(ChatColor.BLACK, DyeColor.BLACK, VanillaItem.BLACK_WOOL),\n        DARK_GRAY(ChatColor.DARK_GRAY, DyeColor.GRAY, VanillaItem.GRAY_WOOL),\n        LIGHT_GRAY(ChatColor.GRAY, DyeColor.valueOf(Version.isAtLeast(Version.MC1_13) ? \"LIGHT_GRAY\" : \"SILVER\"), VanillaItem.LIGHT_GRAY_WOOL),\n        WHITE(ChatColor.WHITE, DyeColor.WHITE, VanillaItem.WHITE_WOOL),\n        DARK_GREEN(ChatColor.DARK_GREEN, DyeColor.GREEN, VanillaItem.GREEN_WOOL),\n        LIGHT_GREEN(ChatColor.GREEN, DyeColor.LIME, VanillaItem.LIME_WOOL),\n        CYAN(ChatColor.DARK_AQUA, DyeColor.CYAN, VanillaItem.CYAN_WOOL),\n        DARK_BLUE(ChatColor.DARK_BLUE, DyeColor.BLUE, VanillaItem.BLUE_WOOL),\n        LIGHT_BLUE(ChatColor.AQUA, DyeColor.LIGHT_BLUE, VanillaItem.LIGHT_BLUE_WOOL),\n        PURPLE(ChatColor.DARK_PURPLE, DyeColor.PURPLE, VanillaItem.PURPLE_WOOL),\n        MAGENTA(ChatColor.LIGHT_PURPLE, DyeColor.MAGENTA, VanillaItem.MAGENTA_WOOL),\n        DARK_RED(ChatColor.DARK_RED, DyeColor.BROWN, VanillaItem.BROWN_WOOL),\n        LIGHT_RED(ChatColor.RED, DyeColor.RED, VanillaItem.RED_WOOL),\n        ORANGE(ChatColor.GOLD, DyeColor.ORANGE, VanillaItem.ORANGE_WOOL),\n        YELLOW(ChatColor.YELLOW, DyeColor.YELLOW, VanillaItem.YELLOW_WOOL),\n        PINK(ChatColor.BLUE, DyeColor.PINK, VanillaItem.PINK_WOOL);\n\n        private ChatColor chat;\n        private DyeColor dye;\n        private VanillaItem woolMaterial;\n\n        Color(ChatColor chat, DyeColor dye, VanillaItem woolMaterial) {\n            this.chat = chat;\n            this.dye = dye;\n            this.woolMaterial = woolMaterial;\n        }\n\n        /**\n         * Returns the ChatColor.\n         *\n         * @return the ChatColor\n         */\n        public ChatColor getChatColor() {\n            return chat;\n        }\n\n        /**\n         * Returns the DyeColor.\n         *\n         * @return the DyeColor\n         */\n        public DyeColor getDyeColor() {\n            return dye;\n        }\n\n        /**\n         * Returns the RGB value.\n         *\n         * @return the RGB value\n         */\n        public int getRGBColor() {\n            return dye.getColor().asRGB();\n        }\n\n        /**\n         * Returns the wool material.\n         *\n         * @return the wool material\n         */\n        public VanillaItem getWoolMaterial() {\n            return woolMaterial;\n        }\n\n        /**\n         * Returns the GroupColor matching the ChatColor or null if none exists.\n         *\n         * @param color the ChatColor to check\n         * @return the GroupColor matching the ChatColor or null if none exists\n         */\n        public static Color getByChatColor(ChatColor color) {\n            for (Color groupColor : values()) {\n                if (groupColor.chat == color) {\n                    return groupColor;\n                }\n            }\n            return null;\n        }\n\n        /**\n         * Returns the GroupColor matching the DyeColor or null if none exists.\n         *\n         * @param color the DyeColor to check\n         * @return the GroupColor matching the DyeColor or null if none exists.\n         */\n        public static Color getByDyeColor(DyeColor color) {\n            for (Color groupColor : values()) {\n                if (groupColor.dye == color) {\n                    return groupColor;\n                }\n            }\n            return null;\n        }\n\n        /**\n         * Returns the GroupColor matching the wool material or null if none exists.\n         *\n         * @param wool the wool material to check\n         * @return the GroupColor matching the wool material or null if none exists\n         */\n        public static Color getByWoolType(ExItem wool) {\n            for (Color groupColor : values()) {\n                if (groupColor.woolMaterial == wool) {\n                    return groupColor;\n                }\n            }\n            return null;\n        }\n\n    }\n\n    /**\n     * Returns the ID.\n     *\n     * @return the ID\n     */\n    int getId();\n\n    /**\n     * Returns the formatted name.\n     * <p>\n     * This is the name used e.g. in messages.\n     *\n     * @return the formatted name\n     */\n    String getName();\n\n    /**\n     * Returns the raw, unformatted name.\n     * <p>\n     * This is the name used e.g. in command arguments.\n     *\n     * @return the raw, unformatted name\n     */\n    String getRawName();\n\n    /**\n     * Sets the name.\n     *\n     * @param name the name\n     */\n    void setName(String name);\n\n    /**\n     * Sets the name to a default value taken from the color.\n     * <p>\n     * In the default implementation, this is nameOfTheColor#{@link #getId()}\n     *\n     * @param color the color\n     */\n    default void setName(Color color) {\n        setName(color.toString());\n    }\n\n    /**\n     * The player who has permission to manage the group.\n     *\n     * @return the player who has permission to manage the group\n     */\n    Player getLeader();\n\n    /**\n     * Sets the leader to another group member.\n     *\n     * @param player the new leader\n     */\n    void setLeader(Player player);\n\n    /**\n     * Returns a PlayerCollection of the group members\n     *\n     * @return a PlayerCollection of the group members\n     */\n    PlayerCollection getMembers();\n\n    /**\n     * Adds a player to the group.\n     * <p>\n     * The default implemenation calls {@link #addMember(Player, boolean)} with messages set to true.\n     *\n     * @param player the player to add\n     */\n    default void addMember(Player player) {\n        addMember(player, true);\n    }\n\n    /**\n     * Adds a player to the group.\n     *\n     * @param player  the player to add\n     * @param message if messages shall be sent\n     */\n    void addMember(Player player, boolean message);\n\n    /**\n     * Removes a player from the group.\n     * <p>\n     * The default implemenation calls {@link #removeMember(Player, boolean)} with messages set to true.\n     *\n     * @param player the player to add\n     */\n    default void removeMember(Player player) {\n        removeMember(player, true);\n    }\n\n    /**\n     * Removes a player from the group.\n     *\n     * @param player  the player to add\n     * @param message if messages shall be sent\n     */\n    void removeMember(Player player, boolean message);\n\n    /**\n     * Returns a PlayerCollection of the players who are invited to join the group but did not yet do so.\n     *\n     * @return a PlayerCollection of the players who are invited to join the group but did not yet do so\n     */\n    PlayerCollection getInvitedPlayers();\n\n    /**\n     * Invites a player to join the group.\n     *\n     * @param player  the player to invite\n     * @param message if messages shall be sent\n     */\n    void addInvitedPlayer(Player player, boolean message);\n\n    /**\n     * Removes an invitation priviously made for a player to join the group.\n     *\n     * @param player  the player to uninvite\n     * @param message if messages shall be sent\n     */\n    void removeInvitedPlayer(Player player, boolean message);\n\n    /**\n     * Removes all invitations for players who are not online.\n     */\n    void clearOfflineInvitedPlayers();\n\n    /**\n     * Returns the game of the game world the group is in.\n     *\n     * @return the game of the game world the group is in.\n     */\n    Game getGame();\n\n    /**\n     * Returns the game world the group is in.\n     *\n     * @return the game world the group is in\n     */\n    default GameWorld getGameWorld() {\n        return getGame() != null ? getGame().getWorld() : null;\n    }\n\n    /**\n     * Returns the dungeon the group is playing or has remembered to play next.\n     * <p>\n     * The latter is for example used when a group is created by a group sign sothat a portal or the auto-join function knows where to send the group.\n     *\n     * @return the dungeon the group is playing or has remembered to play next\n     */\n    Dungeon getDungeon();\n\n    /**\n     * Returns if the group is already playing its remembered {@link #getDungeon() dungeon}.\n     *\n     * @return if the group is already playing its remembered {@link #getDungeon() dungeon}\n     */\n    boolean isPlaying();\n\n    /**\n     * Returns the rewards that are memorized for the group. These are given when the game is finished.\n     *\n     * @return the rewards\n     */\n    List<Reward> getRewards();\n\n    /**\n     * Memorizes the given reward for the group. These are given when the game is finished.\n     *\n     * @param reward the reward\n     */\n    void addReward(Reward reward);\n\n    /**\n     * Removes the given reward.\n     *\n     * @param reward the reward\n     */\n    void removeReward(Reward reward);\n\n    /**\n     * Returns the score number, which is used for capture the flag and similar game types.\n     *\n     * @return the score number\n     */\n    int getScore();\n\n    /**\n     * Sets the score of this group to a new value.\n     *\n     * @param score the value\n     */\n    void setScore(int score);\n\n    /**\n     * Returns the initial amount of lives or -1 if group lives are not used.\n     *\n     * @return the initial amount of lives or -1 if group lives are not used\n     */\n    int getInitialLives();\n\n    /**\n     * Sets the initial amount of lives.\n     * <p>\n     * The value must be &gt;=0 or -1, which means unlimited lives.\n     *\n     * @param lives the new amount of lives known as the initial amount\n     */\n    void setInitialLives(int lives);\n\n    /**\n     * Returns the amount of lives the group currently has left or -1 if group lives are not used.\n     *\n     * @return the amount of lives the group currently has left or -1 if group lives are not used\n     */\n    int getLives();\n\n    /**\n     * Sets the amount of lives the group currently has left.\n     * <p>\n     * The value must be &gt;=0 or -1, which means unlimited lives.\n     *\n     * @param lives the amount of lives the group currently has left\n     */\n    void setLives(int lives);\n\n    /**\n     * Returns true if all players of the group have finished the game; false if not.\n     *\n     * @return true if all players of the group have finished the game; false if not\n     */\n    boolean isFinished();\n\n    /**\n     * Disbands the group.\n     */\n    void delete();\n\n    /**\n     * Sends a message to all players in the group.\n     * <p>\n     * Supports color codes.\n     *\n     * @param message the message to sent\n     * @param except  Players who shall not receive the message\n     */\n    default void sendMessage(String message, Player... except) {\n        members:\n        for (Player player : getMembers().getOnlinePlayers()) {\n            if (!player.isOnline()) {\n                continue;\n            }\n            for (Player nope : except) {\n                if (player == nope) {\n                    continue members;\n                }\n            }\n            MessageUtil.sendMessage(player, message);\n        }\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/AbstractDSign.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.ChatColor;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\n\n/**\n * Skeletal implementation of {@link DungeonSign}.\n *\n * @author Daniel Saukel\n */\npublic abstract class AbstractDSign implements DungeonSign {\n\n    public static final String ERROR_0 = ChatColor.DARK_RED + \"## ERROR ##\";\n    public static final String ERROR_1 = ChatColor.WHITE + \"Please\";\n    public static final String ERROR_2 = ChatColor.WHITE + \"contact an\";\n    public static final String ERROR_3 = ChatColor.WHITE + \"Admin!\";\n\n    protected DungeonsAPI api;\n    private Sign sign;\n    private String[] lines;\n    private InstanceWorld instance;\n    String worldName;\n    private LogicalExpression triggerExpression;\n    private List<Trigger> triggers;\n    private boolean initialized;\n    private boolean erroneous;\n\n    protected AbstractDSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        this.api = api;\n        this.sign = sign;\n        this.lines = lines;\n        this.instance = instance;\n        worldName = instance.getWorld().getName();\n    }\n\n    @Override\n    public Sign getSign() {\n        return sign;\n    }\n\n    @Override\n    public String[] getLines() {\n        return lines;\n    }\n\n    @Override\n    public Location getLocation() {\n        return sign.getLocation();\n    }\n\n    @Override\n    public EditWorld getEditWorld() {\n        return instance instanceof EditWorld ? (EditWorld) instance : null;\n    }\n\n    @Override\n    public GameWorld getGameWorld() {\n        return instance instanceof GameWorld ? (GameWorld) instance : null;\n    }\n\n    @Override\n    public LogicalExpression getTriggerExpression() {\n        return triggerExpression;\n    }\n\n    @Override\n    public List<Trigger> getTriggers() {\n        if (triggers != null) {\n            return triggers;\n        }\n        List<LogicalExpression> contents = triggerExpression.getContents(true);\n        triggers = new ArrayList<>(contents.size());\n        contents.forEach(e -> triggers.add(e.toTrigger(api, this, false)));\n        return triggers;\n    }\n\n    @Override\n    public boolean isInitialized() {\n        return initialized;\n    }\n\n    @Override\n    public void updateTriggers(Trigger lastFired) {\n        if (isErroneous()) {\n            return;\n        }\n\n        if (!triggerExpression.isSatisfied()) {\n            return;\n        }\n\n        try {\n            trigger(lastFired != null ? lastFired.getTriggeringPlayer() : null);\n        } catch (Exception exception) {\n            markAsErroneous(\"An error occurred while triggering a sign of the type \" + getName()\n                    + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n            exception.printStackTrace();\n        }\n    }\n\n    @Override\n    public boolean setToAir() {\n        sign.getBlock().setType(VanillaItem.AIR.getMaterial());\n        return true;\n    }\n\n    @Override\n    public boolean isErroneous() {\n        return erroneous;\n    }\n\n    @Override\n    public void markAsErroneous(String reason) {\n        erroneous = true;\n        sign.setLine(0, ERROR_0);\n        sign.setLine(1, ERROR_1);\n        sign.setLine(2, ERROR_2);\n        sign.setLine(3, ERROR_3);\n        sign.update();\n\n        MessageUtil.log(api, \"&4A sign at &6\" + sign.getX() + \", \" + sign.getY() + \", \" + sign.getZ() + \"&4 is erroneous!\");\n        MessageUtil.log(api, getName() + \": \" + reason);\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/Button.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * A sign that performs a specific action every time it is triggered. It can have, but typically does not have a state. Consider using {@link Deactivatable} for\n * signs that change themselves when they are triggered.\n * <p>\n * For example, a classes sign with the default interact trigger sets your class every time you punch it.\n *\n * @author Daniel Saukel\n */\npublic abstract class Button extends AbstractDSign {\n\n    protected Button(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * When the sign is triggered without one particular player being the cause.\n     * <p>\n     * <b>Note that the default implementation of {@link #push(org.bukkit.entity.Player)} assumes that the sign does not need player specific behavior and\n     * simply calls this method, while the default implementation of this method assumes that the sign should perform {@link #push(org.bukkit.entity.Player)}\n     * for each player in the game world. This leaves a button sign with a stackoverflow if not one of both methods at least is overriden. Consider using a\n     * {@link Passive} sign instead if you need a sign that simply marks places and ignores being triggered.</b>\n     */\n    public void push() {\n        getGameWorld().getPlayers().forEach(p -> push(p.getPlayer()));\n    }\n\n    /**\n     * When the sign is triggered.\n     * <p>\n     * This is the default {@link #trigger(org.bukkit.entity.Player)} behavior.\n     * <p>\n     * <b>Note that the default implementation of this method assumes that the sign does not need player specific behavior and simply calls {@link #push()},\n     * while the default implementation of {@link #push()} assumes that the sign should perform {@link #push(org.bukkit.entity.Player)} for each player in the\n     * game world. This leaves a button sign with a stackoverflow if not one of both methods at least is overriden. Consider using a {@link Passive} sign\n     * instead if you need a sign that simply marks places and ignores being triggered.</b>\n     *\n     * @param player the player who triggered the sign\n     * @return if the action is done successfully\n     */\n    public boolean push(Player player) {\n        push();\n        return true;\n    }\n\n    /**\n     * This is the same as {@link #push(org.bukkit.entity.Player)}.\n     *\n     * @param player the player who triggered the sign or null if no one in particular triggered it\n     */\n    @Override\n    public void trigger(Player player) {\n        if (player != null) {\n            push(player);\n        } else {\n            push();\n        }\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/Deactivatable.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.xlib.player.PlayerCollection;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * A {@link DungeonSign} that changes its state when triggered.\n *\n * @author Daniel Saukel\n */\npublic abstract class Deactivatable extends AbstractDSign {\n\n    protected boolean active;\n    protected PlayerCollection playersActivated = new PlayerCollection();\n\n    protected Deactivatable(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * Sets the state to active.\n     * <p>\n     * <b>Note that the default implementation of {@link #activate(org.bukkit.entity.Player)} assumes that the sign does not need player specific behavior and\n     * simply calls this method, while the default implementation of this method assumes that the sign should perform\n     * {@link #activate(org.bukkit.entity.Player)} for each player in the game world. This leaves a button sign with a stackoverflow if not one of both methods\n     * at least is overriden. Consider using a {@link Passive} sign instead if you need a sign that simply marks places and ignores being triggered. An\n     * implementation that does not need player specific behavior should set {@link #active} to true.</b>\n     */\n    public void activate() {\n        getGameWorld().getPlayers().forEach(p -> activate(p.getPlayer()));\n    }\n\n    /**\n     * Sets the state to active for the given player.\n     * <p>\n     * <b>Note that the default implementation of this method assumes that the sign does not need player specific behavior and simply calls {@link #activate()},\n     * while the default implementation of {@link #activate()} assumes that the sign should perform {@link #activate(org.bukkit.entity.Player)} for each player\n     * in the game world. This leaves a deactivatable sign with a stackoverflow if not one of both methods at least is overriden. Consider using a\n     * {@link Passive} sign instead if you need a sign that simply marks places and ignores being triggered. An implementation that needs player specific\n     * behavior should add the player to the {@link #playersActivated} collection.</b>\n     *\n     * @param player the player\n     * @return if the action was successful\n     */\n    public boolean activate(Player player) {\n        activate();\n        return true;\n    }\n\n    /**\n     * Sets the state to inactive.\n     * <p>\n     * <b>Note that the default implementation of {@link #deactivate(org.bukkit.entity.Player)} assumes that the sign does not need player specific behavior and\n     * simply calls this method, while the default implementation of this method assumes that the sign should perform\n     * {@link #deactivate(org.bukkit.entity.Player)} for each player in the game world. This leaves a button sign with a stackoverflow if not one of both\n     * methods at least is overriden. Consider using a {@link Passive} sign instead if you need a sign that simply marks places and ignores being triggered. An\n     * implementation that does not need player specific behavior should set {@link #active} to false.</b>\n     */\n    public void deactivate() {\n        getGameWorld().getPlayers().forEach(p -> deactivate(p.getPlayer()));\n    }\n\n    /**\n     * Sets the state to inactive for the given player.\n     * <p>\n     * <b>Note that the default implementation of this method assumes that the sign does not need player specific behavior and simply calls\n     * {@link #deactivate()}, while the default implementation of {@link #deactivate()} assumes that the sign should perform\n     * {@link #deactivate(org.bukkit.entity.Player)} for each player in the game world. This leaves a deactivatable sign with a stackoverflow if not one of both\n     * methods at least is overriden. Consider using a {@link Passive} sign instead if you need a sign that simply marks places and ignores being triggered. An\n     * implementation that needs player specific behavior should remove the player from the {@link #playersActivated} collection.</b>\n     *\n     * @param player the player\n     * @return if the action was successful\n     */\n    public boolean deactivate(Player player) {\n        deactivate();\n        return true;\n    }\n\n    /**\n     * Returns if the sign is currently in its activated state.\n     * <p>\n     * This might not be meaningful if the sign uses {@link #isActive(org.bukkit.entity.Player)}.\n     *\n     * @return if the sign is currently in its activated state\n     */\n    public boolean isActive() {\n        return active;\n    }\n\n    /**\n     * Returns if the sign is activated for the given player.\n     * <p>\n     * <b>Note that the default implementation of this method assumes that the sign does not need player specific behavior and simply calls {@link #isActive()}.\n     * An implementation that needs player specific behavior should check if the {@link #playersActivated} collection contains the player.</b>\n     *\n     * @param player the player\n     * @return if the sign is activated for the given player\n     */\n    public boolean isActive(Player player) {\n        return isActive();\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/DungeonSign.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport org.bukkit.block.Sign;\n\n/**\n * Interface for all dungeon signs.\n *\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic interface DungeonSign extends TriggerListener {\n\n    /**\n     * Returns the name to identify the sign.\n     *\n     * @return the name\n     */\n    String getName();\n\n    /**\n     * Returns the permission node that is required to build a sign of this type.\n     *\n     * @return the build permission\n     */\n    String getBuildPermission();\n\n    /**\n     * Returns if the sign gets initialized when the dungeon is loaded instead of when the game starts.\n     *\n     * @return if the sign gets initialized when the dungeon is loaded instead of when the game starts\n     */\n    boolean isOnDungeonInit();\n\n    /**\n     * Returns if the sign block is breakable after the initialization.\n     *\n     * @return if the sign block is breakable after the initialization\n     */\n    boolean isProtected();\n\n    /**\n     * Returns true if the fourth line of the sign that usually contains the trigger is used differently.\n     *\n     * @deprecated This is overriden by interact signs, but it is strongly advised to stick to the convention to fetch data only from the second and third line.\n     *\n     * @return true if the fourth line of the sign that usually contains the trigger is used differently\n     */\n    @Deprecated\n    default boolean isTriggerLineDisabled() {\n        return false;\n    }\n\n    /**\n     * Returns if the block type of the sign is set to air after the initialization.\n     *\n     * @return if the block type of the sign is set to air after the initialization\n     */\n    boolean isSetToAir();\n\n    /**\n     * Returns the sign that represents event point.\n     * <p>\n     * Use {@link #getLines()} instead to read the raw data of this dungeon sign.\n     *\n     * @return the sign that represents event point\n     */\n    Sign getSign();\n\n    /**\n     * Returns the raw line of this sign at the given index.\n     * <p>\n     * These lines might not be the physical lines of {@link #getSign()}.\n     *\n     * @param index the line index (0-3)\n     * @return the raw lines of this sign in an array with 4 elements\n     */\n    default String getLine(int index) {\n        return getLines()[index];\n    }\n\n    /**\n     * Returns the raw lines of this sign in an array with 4 elements.\n     * <p>\n     * These lines might not be the physical lines of {@link #getSign()}.\n     *\n     * @return the raw lines of this sign in an array with 4 elements\n     */\n    String[] getLines();\n\n    /**\n     * Returns the edit world this sign is in; null if this is in a game world.\n     *\n     * @return the edit world this sign is in; null if this is in a game world\n     */\n    EditWorld getEditWorld();\n\n    /**\n     * Sets the sign to air if it is not erroneous and if its type requires this.\n     * <p>\n     * Signs are usually to be set to air upon initialization, but this is not done automatically because some signs need different behavior. Script signs for\n     * example are not set to air because this would override whatever a block sign in its script does.\n     *\n     * @return if the sign type was set to air\n     */\n    boolean setToAir();\n\n    /**\n     * Returns if the sign is valid.\n     * <p>\n     * A sign is invalid when it lacks needed parameters or if illegal arguments have been entered.\n     *\n     * @return if the sign is valid\n     */\n    boolean validate();\n\n    /**\n     * Returns if the sign is erroneous.\n     *\n     * @return if the sign is erroneous\n     */\n    boolean isErroneous();\n\n    /**\n     * Set a placeholder to show that the sign is setup incorrectly.\n     *\n     * @param reason the reason why the sign is marked as erroneous\n     */\n    void markAsErroneous(String reason);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/Passive.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * A sign that does not do anything on its own. Its function is mostly to mark locations or blocks, like lobby or bed signs.\n *\n * @author Daniel Saukel\n */\npublic abstract class Passive extends AbstractDSign {\n\n    protected Passive(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * Does nothing.\n     *\n     * @param lastFired unused\n     */\n    @Override\n    public final void updateTriggers(Trigger lastFired) {\n    }\n\n    /**\n     * Does nothing.\n     *\n     * @param player unused\n     */\n    @Override\n    public final void trigger(Player player) {\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/Rocker.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * A sign that has a <i>deactivated</i> and an <i>activated state</i> and can switch between these two.\n * <p>\n * For example, if a door sign is activated, the door opens - if it is deactivated, the door closes. The state may be set for the whole game world or for the\n * player who triggered the sign depending on the context.\n *\n * @author Daniel Saukel\n */\npublic abstract class Rocker extends Deactivatable {\n\n    protected Rocker(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * Activates the sign if it is not yet active and deactivates it if it is already active.\n     *\n     * @param player the player who triggered the sign or null if no one in particular triggered it\n     */\n    @Override\n    public void trigger(Player player) {\n        if (!isActive()) {\n            if (player != null) {\n                activate(player);\n            } else {\n                activate();\n            }\n        } else {\n            deactivate();\n        }\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/sign/Windup.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.sign;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitTask;\n\n/**\n * A sign with an attached task that does actions in a set interval {@link #n} times, like a mob sign that spawns {@link #n} mobs. It is similar to a\n * {@link Rocker} as it expires (=is deactivated).\n *\n * @author Daniel Saukel\n */\npublic abstract class Windup extends Deactivatable {\n\n    protected double delay = -1;\n    protected double interval = -1;\n    /**\n     * How many times the task is supposed to be executed (unless it is cancelled).\n     */\n    protected int n;\n\n    private Runnable runnable;\n    private BukkitTask task;\n\n    protected Windup(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    /**\n     * Returns the delay before the task runs in seconds. If no delay is specified, this uses the interval.\n     *\n     * @return the delay before the task runs in seconds. If no delay is specified, this uses the interval\n     */\n    public double getDelaySeconds() {\n        return delay != -1 ? delay : interval;\n    }\n\n    /**\n     * Returns the delay before the task runs in ticks. If no delay is specified, this uses the interval.\n     *\n     * @return the delay before the task runs in ticks. If no delay is specified, this uses the interval\n     */\n    public long getDelayTicks() {\n        return delay != -1 ? (long) (delay * 20L) : getIntervalTicks();\n    }\n\n    /**\n     * Returns the task interval in seconds.\n     *\n     * @return the task interval in seconds\n     */\n    public double getIntervalSeconds() {\n        return interval;\n    }\n\n    /**\n     * Returns the task interval in ticks.\n     *\n     * @return the task interval in ticks\n     */\n    public long getIntervalTicks() {\n        return (long) (interval * 20L);\n    }\n\n    /**\n     * Returns the underlying task if it has started yet or null if not.\n     *\n     * @return the underlying task if it has started yet or null if not\n     */\n    public BukkitTask getTask() {\n        return task;\n    }\n\n    /**\n     * Starts the runnable.\n     */\n    public void startTask() {\n        task = Bukkit.getScheduler().runTaskTimer(api, runnable, getDelayTicks(), getIntervalTicks());\n    }\n\n    /**\n     * Returns the runnable.\n     *\n     * @return the runnable\n     */\n    public Runnable getRunnable() {\n        return runnable;\n    }\n\n    /**\n     * Sets the runnable.\n     *\n     * @param runnable the runnable\n     */\n    public void setRunnable(Runnable runnable) {\n        this.runnable = runnable;\n    }\n\n    /**\n     * Returns how many times the task is supposed to be executed (like in SIGMA notation).\n     *\n     * @return how many times the task is supposed to be executed (like in SIGMA notation)\n     */\n    public int getN() {\n        return n;\n    }\n\n    /**\n     * Sets how many times the task is supposed to be executed (like in SIGMA notation).\n     *\n     * @param n the new amount of runs\n     */\n    public void setN(int n) {\n        this.n = n;\n    }\n\n    @Override\n    public void activate() {\n        if (interval <= 0) {\n            for (int k = 0; k < n; k++) {\n                runnable.run();\n            }\n        } else {\n            active = true;\n            startTask();\n        }\n    }\n\n    /**\n     * Cancels the {@link #getTask() task}.\n     */\n    @Override\n    public void deactivate() {\n        active = false;\n        if (getTask() != null) {\n            getTask().cancel();\n        }\n    }\n\n    /**\n     * Activates the sign if it is not yet active and deactivates it if it is already active.\n     *\n     * @param player the player who triggered the sign or null if no one in particular triggered it\n     */\n    @Override\n    public void trigger(Player player) {\n        if (!isActive()) {\n            if (player != null) {\n                activate(player);\n            } else {\n                activate();\n            }\n        }\n    }\n\n    /**\n     * Use this method to ensure that its world still exists.\n     *\n     * @return if the world is already finished\n     */\n    public boolean isWorldFinished() {\n        return Bukkit.getWorld(worldName) == null;\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/trigger/AbstractTrigger.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.event.trigger.TriggerUnregistrationEvent;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\n\n/**\n * Skeletal implementation of {@link Trigger}.\n *\n * @author Daniel Saukel, Frank Baumann, Milan Albrecht\n */\npublic abstract class AbstractTrigger implements Trigger {\n\n    private Set<TriggerListener> listeners = new HashSet<>();\n    private GameWorld gameWorld;\n    private LogicalExpression expression;\n    private String value;\n    private Player player;\n\n    protected AbstractTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        listeners.add(owner);\n        gameWorld = owner.getGameWorld();\n        this.expression = expression;\n        expression.setTrigger(this);\n        this.value = value;\n    }\n\n    @Override\n    public String getValue() {\n        return value;\n    }\n\n    @Override\n    public GameWorld getGameWorld() {\n        return gameWorld;\n    }\n\n    @Override\n    public boolean isTriggered() {\n        return expression.isSatisfied();\n    }\n\n    @Override\n    public void setTriggered(boolean triggered) {\n        expression.setSatisfied(triggered);\n    }\n\n    @Override\n    public Player getTriggeringPlayer() {\n        return player;\n    }\n\n    @Override\n    public void setTriggeringPlayer(Player player) {\n        this.player = player;\n    }\n\n    @Override\n    public Set<TriggerListener> getListeners() {\n        return listeners;\n    }\n\n    @Override\n    public boolean addListener(TriggerListener owner) {\n        return listeners.add(owner);\n    }\n\n    @Override\n    public boolean removeListener(TriggerListener listener) {\n        return listeners.remove(listener);\n    }\n\n    @Override\n    public boolean unregisterTrigger() {\n        TriggerUnregistrationEvent event = new TriggerUnregistrationEvent(this);\n        Bukkit.getPluginManager().callEvent(event);\n        if (!event.isCancelled()) {\n            return gameWorld.unregisterTrigger(this);\n        }\n        return false;\n    }\n\n    @Override\n    public void updateListeners() {\n        for (TriggerListener dSign : listeners.toArray(TriggerListener[]::new)) {\n            dSign.updateTriggers(this);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{expression=\" + expression + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/trigger/LogicalExpression.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This class represents a logical expression of elements in an AND and OR relation to each other.\n * <p>\n * Valid operators are:\n * <ul>\n * <li>, = AND</li>\n * <li>/ = OR</li>\n * <li>() = priority</li>\n * </ul>\n * OR is prioritized over AND if no brackets indicate otherwise.\n * <p>\n * Logical expressions tolerate spaces and redundant operators, but no wrong brackets.\n *\n * @author Daniel Saukel\n */\npublic class LogicalExpression {\n\n    private enum ComponentType {\n        FIRST,\n        AND,\n        OR;\n\n        @Override\n        public String toString() {\n            return this == FIRST ? \"\" : name() + \":\";\n        }\n    }\n\n    /**\n     * A satisfied, empty expression.\n     */\n    public static final LogicalExpression EMPTY = new LogicalExpression(ComponentType.FIRST, \"\");\n\n    static {\n        EMPTY.satisfied = true;\n    }\n\n    private ComponentType type;\n    private String text;\n    private Trigger trigger;\n    private List<LogicalExpression> contents;\n    private boolean satisfied;\n\n    private LogicalExpression(ComponentType type, String text) {\n        this.type = type;\n        this.text = text;\n        contents = new ArrayList<>();\n    }\n\n    /**\n     * Interpretes the given string as a expression.\n     *\n     * @param string the string to parse\n     * @throws IllegalArgumentException if the string is not a valid logical expression.\n     * @return a expression that represents the given string; null if erroneous\n     */\n    public static LogicalExpression parse(String string) {\n        if (string == null || string.isBlank()) {\n            return null;\n        }\n        string = string.replace(\" \", \"\");\n\n        // Crop redundant end\n        int i = string.length() - 1;\n        while (string.charAt(i) == ',' || string.charAt(i) == '/') {\n            i--;\n        }\n\n        string = string.substring(0, i + 1);\n        return parse(ComponentType.FIRST, string);\n    }\n\n    private static LogicalExpression parse(ComponentType type, String string) {\n        LogicalExpression root = new LogicalExpression(type, string);\n        if (!string.contains(\",\") && !string.contains(\"/\")) {\n            return root;\n        }\n\n        int i = 0;\n        int bracketStart = -1;\n        int innerBrackets = 0;\n        int currentCompStart = 0;\n        boolean bracketLegal = true;\n        ComponentType currentCompType = ComponentType.FIRST;\n        while (string.length() > i) {\n            char c = string.charAt(i);\n            if (c == '(') {\n                if (!bracketLegal) {\n                    throw new IllegalArgumentException(\"Bracket on illegal position at index \" + i);\n                }\n                if (bracketStart == -1) {\n                    bracketStart = i + 1; // One after bracket start\n                } else {\n                    innerBrackets++;\n                }\n\n            } else if (c == ')') {\n                if (bracketStart == -1) {\n                    throw new IllegalArgumentException(\"Closed bracket before it was opened at index \" + i);\n                }\n                innerBrackets--;\n                if (innerBrackets == -1) {\n                    // Bracket closed\n                    LogicalExpression subexpression = parse(currentCompType, string.substring(bracketStart, i));\n                    if (bracketStart == 1 && i == string.length() - 1) {\n                        // Bracket encapsulates the whole string and was redundant\n                        return subexpression;\n                    }\n\n                    root.contents.add(subexpression);\n                    innerBrackets = 0;\n                    bracketStart = -1;\n                    currentCompStart = i + 2;\n                }\n                bracketLegal = false;\n\n            } else if (bracketStart == -1 && (c == ',' || c == '/')) {\n                if (currentCompStart == i) {\n                    // Multiple commas / leading comma -> ignore\n                    currentCompStart++;\n                } else {\n                    // Component had ended\n                    if (i > currentCompStart) {\n                        // Component had ended\n                        root.contents.add(parse(currentCompType, string.substring(currentCompStart, i)));\n                    }\n                    currentCompStart = i + 1;\n                    currentCompType = c == ',' ? ComponentType.AND : ComponentType.OR;\n                }\n                bracketLegal = true;\n            } else {\n                bracketLegal = false;\n            }\n            i++;\n        }\n        if (innerBrackets != 0 || bracketStart != -1) {\n            throw new IllegalArgumentException(\"Bracket never closed\");\n        }\n        // Last component\n        if (currentCompStart < string.length()) {\n            LogicalExpression comp = parse(currentCompType, string.substring(currentCompStart, i));\n            if (comp != null) {\n                root.contents.add(comp);\n            }\n        }\n        return root;\n    }\n\n    /**\n     * Returns the text this expression wraps.\n     * <p>\n     * This is not guaranteed to be equal to the string that had been passed to {@link #parse(java.lang.String)} as it may be stripped from redundant symbols.\n     *\n     * @return the text this expression wraps\n     */\n    public String getText() {\n        return text;\n    }\n\n    /**\n     * Returns if this expression only consists of exactly one component.\n     *\n     * @return if this expression only consists of exactly one component\n     */\n    public boolean isAtomic() {\n        return contents.isEmpty();\n    }\n\n    void setTrigger(Trigger trigger) {\n        this.trigger = trigger;\n    }\n\n    /**\n     * Creates a {@link Trigger} from an atomic element of the expression.\n     *\n     * @param api      the {@link DungeonsAPI} reference; not null\n     * @param listener the listener that belongs to the trigger; not null\n     * @param generic  if a generic trigger should be created if there is no specific trigger identifier (e.g. second line of a trigger sign)\n     * @throws IllegalArgumentException if the listener is null or not in a {@link GameWorld}\n     * @return the {@link Trigger} the string represents; null if there is none.\n     */\n    public Trigger toTrigger(DungeonsAPI api, TriggerListener listener, boolean generic) {\n        if (trigger != null) {\n            return trigger;\n        }\n        if (!isAtomic()) {\n            return null;\n        }\n        if (listener == null || listener.getGameWorld() == null) {\n            throw new IllegalArgumentException(\"Listener must not be null and must be in a game world\");\n        }\n        listener.getGameWorld().createTrigger(listener, this);\n        return trigger;\n    }\n\n    /**\n     * Returns a List of the contents of this expression.<p>\n     * Changes made to this list do not update the expression.\n     *\n     * @param deep if true, brackets are resolved to atomic components; if false, brackets are one element in the list\n     * @return a List of the contents the contents of this expression\n     */\n    public List<LogicalExpression> getContents(boolean deep) {\n        if (!deep || isAtomic()) {\n            return new ArrayList<>(contents);\n        }\n        List<LogicalExpression> atomicContents = new ArrayList<>();\n        for (LogicalExpression comp : contents) {\n            if (comp.isAtomic()) {\n                atomicContents.add(comp);\n            } else {\n                atomicContents.addAll(comp.getContents(true));\n            }\n        }\n        return atomicContents;\n    }\n\n    /**\n     * Returns if this expression is satisfied.\n     *\n     * @return if this expression is satisfied\n     */\n    public boolean isSatisfied() {\n        if (isAtomic()) {\n            return satisfied;\n        }\n\n        int[] orChains = new int[contents.size()];\n        int ors = 0;\n        boolean inChain = false;\n        boolean chainSatisfied = false;\n        // Check OR chains, return false if NONE is satisfied\n        for (int i = 1; contents.size() > i; i++) {\n            if (contents.get(i).type != ComponentType.OR) {\n                if (inChain) {\n                    if (!chainSatisfied) {\n                        return false;\n                    }\n                    orChains[ors] = i - 1; // write to even index\n                    inChain = false;\n                    chainSatisfied = false;\n                    ors++; // switch to even index for possible upcoming chain\n                }\n                continue;\n            }\n            if (!inChain) {\n                inChain = true;\n                orChains[ors] = i - 1; // write to even index\n                chainSatisfied = contents.get(i - 1).isSatisfied();\n                ors++;                 // switch to uneven index\n            } else {\n                orChains[ors] = i;     // update uneven index\n            }\n            if (contents.get(i).isSatisfied()) {\n                chainSatisfied = true;\n            }\n        }\n        if (inChain) {\n            if (!chainSatisfied) {\n                return false;\n            }\n            orChains[ors] = contents.size() - 1;\n        }\n\n        // Check AND chains, return false if ONE IS NOT satisfied\n        int i = 0, j = 0;\n        while (contents.size() > i) {\n            if (!(orChains[j] == 0 && orChains[j + 1] == 0)) {\n                while (i < orChains[j]) {\n                    if (!contents.get(i).isSatisfied()) {\n                        return false;\n                    }\n                    i++;\n                }\n                j++;\n                i = orChains[j] + 1;\n                j++;\n\n            } else {\n                if (!contents.get(i).isSatisfied()) {\n                    return false;\n                }\n                i++;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Sets the value of an atomic expression to the given boolean.\n     * <p>\n     * If the expression is not atomic, update the atomic components instead.\n     *\n     * @param satisfied if the expression is satisfied\n     * @return if the expression could be updated (false if the expression is not atomic)\n     */\n    public boolean setSatisfied(boolean satisfied) {\n        if (!isAtomic()) {\n            return false;\n        }\n        this.satisfied = satisfied;\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        if (contents.isEmpty()) {\n            return type + text + (isSatisfied() ? \"(t)\" : \"(f)\");\n        }\n        return type + contents.toString() + (isSatisfied() ? \"(t)\" : \"(f)\");\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/trigger/Trigger.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.trigger;\n\nimport com.google.common.collect.Sets;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.event.trigger.TriggerActionEvent;\nimport static de.erethon.dungeonsxl.api.trigger.TriggerTypeKey.*;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.List;\nimport java.util.Set;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\n\n/**\n * A condition to fulfill in order to trigger a {@link TriggerListener} such as a {@link de.erethon.dungeonsxl.api.sign.DungeonSign}.\n * <p>\n * Implementations of this interface must always include a constructor of the types (DungeonsAPI, TriggerListener, LogicalExpression, String).\n *\n * @author Daniel Saukel\n */\npublic interface Trigger {\n\n    /**\n     * A Set of trigger types that are looked up in their game world before they are created, and, if an equal trigger already exists, returned instead of a new\n     * one. For example, an instance trigger is not created when its entered into the trigger line of a listening sign, but when an interact sign explicitly\n     * creates it.\n     *\n     * @see GameWorld#createTrigger(TriggerListener, LogicalExpression)\n     */\n    static final Set<Character> IDENTIFIABLE = Sets.newHashSet(GENERIC, INTERACT, MOB, PROGRESS, USE_ITEM, WAVE);\n\n    /**\n     * Constructs a subtype of Trigger.\n     * <p>\n     * Implementations of this interface must always include a constructor of the types (DungeonsAPI, TriggerListener, LogicalExpression, String).\n     *\n     * @param <T>        the type of the trigger\n     * @param typeKey    the key that represents the type on dungeon signs and in the registry, see {@link TriggerTypeKey}\n     * @param api        the API instance; not null\n     * @param owner      an object that listens to the trigger\n     * @param expression the atomic expression that the trigger uses; not null\n     * @param value      the value of the trigger. This is the text of the expression without the type key; not null\n     * @see GameWorld#createTrigger(TriggerListener, LogicalExpression)\n     * @see AbstractTrigger#AbstractTrigger(DungeonsAPI, TriggerListener, LogicalExpression, String)\n     * @return the constructed Trigger object\n     */\n    static <T extends Trigger> T construct(char typeKey, DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        Class<T> clss = null;\n        try {\n            clss = (Class<T>) api.getTriggerRegistry().get(typeKey);\n            return construct(clss, api, owner, expression, value);\n        } catch (Exception exception) {\n            MessageUtil.log(api, \"&4It looks like the trigger \\\"\" + typeKey + \"\\\"/\" + clss + \" was not registered correctly:\");\n            exception.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Constructs a subtype of Trigger.\n     * <p>\n     * Implementations of this interface must always include a constructor of the types (DungeonsAPI, TriggerListener, LogicalExpression, String).\n     *\n     * @param <T>        the type of the trigger\n     * @param clss       the implementation Class object\n     * @param api        the API instance\n     * @param owner      an object that listens to the trigger\n     * @param expression the atomic expression that the trigger uses; not null\n     * @param value      the value of the trigger. This is the text of the expression without the type key; not null\n     * @see GameWorld#createTrigger(TriggerListener, LogicalExpression)\n     * @see AbstractTrigger#AbstractTrigger(DungeonsAPI, TriggerListener, LogicalExpression, String)\n     * @return the constructed Trigger object\n     */\n    static <T extends Trigger> T construct(Class<T> clss, DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        try {\n            Constructor constructor = clss.getConstructor(DungeonsAPI.class, TriggerListener.class, LogicalExpression.class, String.class);\n            return (T) constructor.newInstance(api, owner, expression, value);\n\n        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException\n                | IllegalArgumentException | InvocationTargetException exception) {\n            MessageUtil.log(api, \"&4Could not create a trigger of the type \\\"\" + clss\n                    + \"\\\". A trigger implementation needs a constructor with the types (DungeonsAPI, TriggerListener, LogicalExpression, String).\");\n            exception.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Returns a char that identifies the trigger as one of a specific type.\n     * <p>\n     * Triggers from the default implementations are stored in {@link TriggerTypeKey}.\n     *\n     * @return a char that identifies the trigger as one of a specific type\n     */\n    char getKey();\n\n    /**\n     * Returns the raw value the trigger was initialized with. May contain an identifier or arguments in any shape or form.\n     *\n     * @return the raw value the trigger was initialized with. May contain an identifier or arguments in any shape or form\n     */\n    String getValue();\n\n    /**\n     * The {@link GameWorld} the trigger works in.\n     *\n     * @return the {@link GameWorld} the trigger works in\n     */\n    GameWorld getGameWorld();\n\n    /**\n     * Returns if the trigger is triggered.\n     *\n     * @return if the trigger is triggered\n     */\n    boolean isTriggered();\n\n    /**\n     * Sets if the trigger is triggered.\n     *\n     * @param triggered the state of the trigger\n     */\n    void setTriggered(boolean triggered);\n\n    /**\n     * Returns the last player who triggered the trigger.\n     *\n     * @return the last player who triggered the trigger\n     */\n    Player getTriggeringPlayer();\n\n    /**\n     * Updates {@link #getTriggeringPlayer()} to the given player.\n     *\n     * @param player the player to set\n     */\n    void setTriggeringPlayer(Player player);\n\n    /**\n     * A set of the objects that listen to this trigger.\n     *\n     * @return a set of the objects that listen to this trigger\n     */\n    Set<TriggerListener> getListeners();\n\n    /**\n     * Adds the given object to listen to this trigger.\n     *\n     * @param listener the listener to add\n     * @return if adding the listener was successful\n     */\n    boolean addListener(TriggerListener listener);\n\n    /**\n     * Removes the given listener.\n     *\n     * @param listener the listener to remove\n     * @return if removing the listener was successful\n     */\n    boolean removeListener(TriggerListener listener);\n\n    /**\n     * Unregisters the trigger from the {@link GameWorld}.\n     * <p>\n     * This is used to disable triggers, such as distance triggers removing themselves after the first player got into range.\n     *\n     * @return if unregistering the listener was successful\n     */\n    boolean unregisterTrigger();\n\n    /**\n     * Updates the listeners; to be used when the state of the trigger is updated; by default in {@link #trigger(boolean, Player)}.\n     */\n    void updateListeners();\n\n    /**\n     * Called when the trigger is triggered.\n     * <p>\n     * This method provides default procedures when the conditions of any trigger are fulfilled, such as calling events and updating listeners. Use\n     * {@link #onTrigger(boolean)} to implement specific behavior (such as self-removal for default distance triggers).\n     * <p>\n     * This method does NOT change {@link #isTriggered()}. Due to the difference in behavior of triggers (working button-like, switch-like etc.), this is to be\n     * handled in {@link #onTrigger(boolean)}.\n     *\n     * @param switching        if the action changes the the state of {@link #isTriggered()}\n     * @param triggeringPlayer the player who fulfilled the (last) conditions of this trigger\n     */\n    default void trigger(boolean switching, Player triggeringPlayer) {\n        List<TriggerListener> fired = getListeners().stream()\n                .filter(l -> l.getTriggerExpression().isSatisfied())\n                .toList();\n        TriggerActionEvent event = new TriggerActionEvent(this, fired);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n        setTriggeringPlayer(triggeringPlayer);\n        onTrigger(switching);\n        updateListeners();\n        postTrigger();\n    }\n\n    /**\n     * Called when the trigger is triggered.\n     * <p>\n     * This method can be used to implement specific behavior (such as self-removal for default distance triggers). Call {@link #trigger(boolean, Player)}\n     * instead when the condition to trigger the trigger is fulfilled.\n     *\n     * @param switching if the action changes the the state of {@link #isTriggered()}\n     */\n    void onTrigger(boolean switching);\n\n    /**\n     * Called after listeners are updated.\n     * <p>\n     * This method can be used to implement specific behavior such as reactivating itself after the actions of the listeners are done, such as presence triggers\n     * performing their action every time a player gets close.\n     */\n    default void postTrigger() {\n    }\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/trigger/TriggerListener.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.trigger;\n\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport java.util.List;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Player;\n\n/**\n * Represents things that wait until a trigger is fired and then react to it, such as {@link de.erethon.dungeonsxl.api.sign.DungeonSign}.\n *\n * @author Daniel Saukel\n */\npublic interface TriggerListener {\n\n    /**\n     * Returns a {@link LogicalExpression} of the triggers registered for this listener.\n     *\n     * @return a a {@link LogicalExpression} of the triggers registered for this listener\n     */\n    LogicalExpression getTriggerExpression();\n\n    /**\n     * Returns a deep List of triggers of this listener.\n     * <p>\n     * WARNING: Do not iterate and check if each is satisfied to get if the sign should fire. Use {@link #getTriggerExpression()} instead.\n     *\n     * @return a deep List of triggers of this listener\n     */\n    List<Trigger> getTriggers();\n\n    /**\n     * Returns if the listener has triggers.\n     *\n     * @return if the listener has triggers\n     */\n    default boolean hasTriggers() {\n        return getTriggerExpression() != null;\n    }\n\n    /**\n     * The location of this listener.\n     *\n     * @return the location of this listener\n     */\n    Location getLocation();\n\n    /**\n     * Returns the game world this listener is in; null if this is in an edit world.\n     *\n     * @return the game world this listener is in; null if this is in an edit world\n     */\n    GameWorld getGameWorld();\n\n    /**\n     * Returns the game played in the world of this listener.\n     *\n     * @return the game played in the world of this listener\n     */\n    default Game getGame() {\n        if (getGameWorld() == null) {\n            return null;\n        }\n        return getGameWorld().getGame();\n    }\n\n    /**\n     * Makes the listener listen for its triggers if it {@link #hasTriggers()}.\n     * <p>\n     * {@link #trigger(org.bukkit.entity.Player)}s the listener if it does not have any triggers. (Note that some signs have interaction triggers by default,\n     * like ready signs).\n     */\n    void initialize();\n\n    /**\n     * Returns if the listener is {@link #initialize()}d.\n     *\n     * @return if the listener is {@link #initialize()}d\n     */\n    boolean isInitialized();\n\n    /**\n     * Triggers the listener. The effects are defined by the implementation.\n     *\n     * @param player the player who triggered the listener or null if no one in particular triggered it\n     */\n    void trigger(Player player);\n\n    /**\n     * Checks if the triggers of the listener have been triggered. If they all are, the listener itself is triggered.\n     *\n     * @param lastFired the last trigger that has been triggered\n     */\n    void updateTriggers(Trigger lastFired);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/trigger/TriggerTypeKey.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.trigger;\n\n/**\n * Type keys of default triggers.\n *\n * @author Daniel Saukel\n */\npublic class TriggerTypeKey {\n\n    public static final char DISTANCE = 'D';\n    public static final char FORTUNE = 'F';\n    public static final char INTERACT = 'I';\n    /**\n     * The terms \"generic\" and \"sign trigger\" are used synonymously. Trigger strings without prefix default to generic triggers.\n     */\n    public static final char GENERIC = 'T';\n    public static final char MOB = 'M';\n    public static final char PRESENCE = 'P';\n    @Deprecated\n    public static final char PROGRESS = 'P';\n    public static final char REDSTONE = 'R';\n    public static final char USE_ITEM = 'U';\n    @Deprecated\n    public static final char WAVE = 'W';\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/world/EditWorld.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.world;\n\nimport org.bukkit.block.Block;\n\n/**\n * A raw resource world instance to edit the dungeon map. There is never more than one edit world per resource world.\n * <p>\n * An edit world is not equal to a {@link de.erethon.dungeonsxl.api.dungeon.Dungeon}.\n *\n * @author Daniel Saukel\n */\npublic interface EditWorld extends InstanceWorld {\n\n    /**\n     * Registers the block as a {@link de.erethon.dungeonsxl.api.sign.DungeonSign} sothat it can later be saved persistently.\n     *\n     * @param block a DungeonSign block\n     */\n    void registerSign(Block block);\n\n    /**\n     * Saves the sign data and overrides the resource with the changes.\n     */\n    void save();\n\n    @Override\n    default void delete() {\n        delete(true);\n    }\n\n    /**\n     * Deletes this edit instance.\n     *\n     * @param save whether this world should be {@link #save()}ed\n     */\n    void delete(boolean save);\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/world/GameWorld.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.world;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport java.util.Collection;\nimport java.util.List;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\n\n/**\n * A playable resource instance. There may be any amount of GameWorlds per {@link ResourceWorld}.\n * <p>\n * A game world is not equal to a {@link de.erethon.dungeonsxl.api.dungeon.Dungeon}.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: [gameblock, secure objects, classes signs, mobs, triggers] methods, getMobCount(), setPlaying(), startGame(), listener methods\npublic interface GameWorld extends InstanceWorld {\n\n    enum Type {\n        START_FLOOR,\n        END_FLOOR,\n        DEFAULT\n    }\n\n    /**\n     * Returns the {@link Type} of this GameWorld.\n     *\n     * @return the {@link Type} of this GameWorld\n     */\n    Type getType();\n\n    /**\n     * Sets the {@link Type} of this GameWorld.\n     *\n     * @param type the type\n     */\n    void setType(Type type);\n\n    /**\n     * Returns the game that is played in the game world.\n     *\n     * @return the game that is played in the game world\n     */\n    Game getGame();\n\n    /**\n     * Returns the dungeon that the game world is part of.\n     * <p>\n     * Note: While a {@link ResourceWorld} may be part of multiple dungeons, an instance is instantiated per game and thus has just one dungeon.\n     *\n     * @return the dungeon that the game world is part of\n     */\n    Dungeon getDungeon();\n\n    /**\n     * Creates a trigger represented by the given atomic expression.\n     * <p>\n     * For example, if the expression may wrap the string \"D 10\", but not \"D 10, M ZOMBIE\".\n     * <p>\n     * Use {@link #createTriggers(TriggerListener, LogicalExpression)} to get an array of all {@link Trigger} objects for a compound expression (such as just \"D\n     * 10, M ZOMBIE\").\n     *\n     * @param owner      the {@link TriggerListener} that the trigger belongs to\n     * @param expression the expression; must be {@link LogicalExpression#isAtomic() atomic}.\n     * @throws IllegalArgumentException if the expression is not atomic\n     * @throws IllegalStateException    if the owner is not in a game world\n     * @return a trigger represented by the given atomic expression; null if the expression is {@link LogicalExpression#EMPTY}.\n     */\n    Trigger createTrigger(TriggerListener owner, LogicalExpression expression);\n\n    /**\n     * Creates triggers represented by the given expression.\n     * <p>\n     * For example, if the expression wraps the string \"D 10, M ZOMBIE\", this returns an array where the 0st entry represents \"D 10\" and the 1st \"M ZOMBIE\".\n     * <p>\n     * Use {@link #createTrigger(TriggerListener, LogicalExpression)} to get one {@link Trigger} object for exactly one atomic part (such as just \"D 10\").\n     *\n     * @param owner      the {@link TriggerListener} that the trigger belongs to\n     * @param expression the expression; must be {@link LogicalExpression#isAtomic() atomic}.\n     * @throws IllegalStateException if the owner is not in a game world\n     * @return a List of triggers represented by the given expression; an empty List if the expression is {@link LogicalExpression#EMPTY}.\n     */\n    List<Trigger> createTriggers(TriggerListener owner, LogicalExpression expression);\n\n    /**\n     * Returns a Collection of the triggers registered in this world.\n     *\n     * @return a Collection of the triggers registered in this world\n     */\n    Collection<Trigger> getTriggers();\n\n    /**\n     * Returns a Collection of the triggers registered in this world that use the given key.\n     *\n     * @param key the key char\n     * @see de.erethon.dungeonsxl.api.trigger.TriggerTypeKey\n     * @return a Collection of the triggers registered in this world that use the given key.\n     */\n    Collection<Trigger> getTriggersFromKey(char key);\n\n    /**\n     * Unregisters the given trigger, which prevents them from firing unless explicitly done in code.\n     *\n     * @param trigger the trigger to unregister\n     * @return if unregistering the trigger was successful\n     */\n    boolean unregisterTrigger(Trigger trigger);\n\n    /**\n     * Returns the living dungeon mobs.\n     *\n     * @return the living dungeon mobs\n     */\n    Collection<DungeonMob> getMobs();\n\n    /**\n     * Registers the given dungeon mob.\n     *\n     * @param mob the mob\n     */\n    void addMob(DungeonMob mob);\n\n    /**\n     * Unregisters the given dungeon mob.\n     *\n     * @param mob the mob\n     */\n    public void removeMob(DungeonMob mob);\n\n    /**\n     * Returns if the game has begun in the game world.\n     *\n     * @return if the game has begun in the game world\n     */\n    boolean isPlaying();\n\n    /**\n     * Returns the start location of the world. This may be set by a start {@link de.erethon.dungeonsxl.api.sign.DungeonSign sign} or, if none exists, the\n     * Vanilla spawn location of the {@link #getWorld() world}.\n     *\n     * @param group each group might have its own start location\n     * @return the start location of the world\n     */\n    Location getStartLocation(PlayerGroup group);\n\n    /**\n     * Returns if it is required to choose a class in order to start the game.\n     *\n     * @return if it is required to choose a class in order to start the game\n     */\n    boolean areClassesEnabled();\n\n    /**\n     * Sets if it is required to choose a class in order to start the game.\n     *\n     * @param enabled if it is required to choose a class in order to start the game\n     */\n    void setClassesEnabled(boolean enabled);\n\n    /**\n     * Returns a collection of the blocks that have been placed by players in the current game.\n     *\n     * @return a collection of the blocks that have been placed by players in the current game\n     */\n    Collection<Block> getPlacedBlocks();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/world/InstanceWorld.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.world;\n\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport java.io.File;\nimport java.util.Collection;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\n\n/**\n * Super interface for worlds that are instantiated by DungeonsXL.\n * <p>\n * An instance world is not equal to a {@link de.erethon.dungeonsxl.api.dungeon.Dungeon}.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: getConfig, exists, setWeather\npublic interface InstanceWorld {\n\n    /**\n     * Returns the name of the resource world of this instance.\n     * <p>\n     * Use {@link #getWorld()}{@link org.bukkit.World#getName() #getName()} to get the name of the instantiated world (like e.g. DXL_Game_1).\n     *\n     * @return the name of the resource world of this instance\n     */\n    String getName();\n\n    /**\n     * Returns the saved map this instance was loaded from.\n     *\n     * @return the saved map this instance was loaded from\n     */\n    ResourceWorld getResource();\n\n    /**\n     * Returns the world folder.\n     *\n     * @return the world folder\n     */\n    File getFolder();\n\n    /**\n     * Returns the wrapped Bukkit world.\n     *\n     * @return the wrapped Bukkit world\n     */\n    World getWorld();\n\n    /**\n     * Returns the ID. This is usually the number in the map name.\n     *\n     * @return the ID\n     */\n    int getId();\n\n    /**\n     * Returns a collection of the signs in this instance.\n     *\n     * @return a collection of the signs in this instance\n     */\n    Collection<DungeonSign> getDungeonSigns();\n\n    /**\n     * Creates a dungeon sign in this instance.\n     *\n     * @param sign  the sign block\n     * @param lines the lines of the sign\n     * @return the created sign\n     */\n    DungeonSign createDungeonSign(Sign sign, String[] lines);\n\n    /**\n     * Removes the given dungeon sign from this instance.\n     *\n     * @param sign the sign\n     */\n    void removeDungeonSign(DungeonSign sign);\n\n    /**\n     * Removes the dungeon sign represented by the given sign block from this instance.\n     *\n     * @param sign the sign block\n     */\n    void removeDungeonSign(Block sign);\n\n    /**\n     * Returns the DungeonSign represented by the given sign block.\n     *\n     * @param sign the sign block\n     * @return the DungeonSign represented by the given sign block\n     */\n    DungeonSign getDungeonSign(Block sign);\n\n    /**\n     * Returns the location of the lobby where players spawn by default when they are teleported into the dungeon.\n     *\n     * @return the location of the lobby where players spawn by default when they are teleported into the dungeon\n     */\n    Location getLobbyLocation();\n\n    /**\n     * Sets the default spawn location of the instance.\n     * <p>\n     * This is not persistent and does not create a lobby sign.\n     *\n     * @param location the location\n     */\n    void setLobbyLocation(Location location);\n\n    /**\n     * Returns the players in the instance.\n     *\n     * @return the players in the instance\n     */\n    Collection<InstancePlayer> getPlayers();\n\n    /**\n     * Sends a message to all players in the instance.\n     *\n     * @param message the message to send\n     */\n    void sendMessage(String message);\n\n    /**\n     * Makes all players leave the world. Attempts to let them leave properly if they are correct DInstancePlayers; teleports them to the spawn if they are not.\n     */\n    void kickAllPlayers();\n\n    /**\n     * Deletes this instance.\n     */\n    void delete();\n\n}\n"
  },
  {
    "path": "api/src/main/java/de/erethon/dungeonsxl/api/world/ResourceWorld.java",
    "content": "/*\n * Copyright (C) 2015-2026 Daniel Saukel\n *\n * This library is free software: you can redistribute it and/or modify it under the\n * terms of the GNU Lesser General Public License as published by the Free Software\n * Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful, but WITHOUT ANY\n * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n * PARTICULAR PURPOSE. See the GNULesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License along with\n * this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.api.world;\n\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport java.io.File;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.World.Environment;\n\n/**\n * A stored world that can be instantiated as an {@link EditWorld} or as a {@link GameWorld}.\n * <p>\n * In the default implementation, these are saved under \"plugins/DungeonsXL/maps/\".\n * <p>\n * A resource world is not equal to a {@link de.erethon.dungeonsxl.api.dungeon.Dungeon}.\n *\n * @author Daniel Saukel\n */\n// Implementation-specific methods: getSignData, generate\npublic interface ResourceWorld {\n\n    /**\n     * Returns the name of this resource world.\n     * <p>\n     * Equals {@link #getFolder()}{@link File#getName() #getName()}.\n     *\n     * @return name of this resource world\n     */\n    String getName();\n\n    /**\n     * Renames the resource world and its folder.\n     *\n     * @param name the new name\n     */\n    void setName(String name);\n\n    /**\n     * Returns the folder where this resource is stored.\n     *\n     * @return the folder where this resource is stored\n     */\n    File getFolder();\n\n    /**\n     * Returns the {@link de.erethon.dungeonsxl.api.dungeon.GameRule}s of this world.\n     * <p>\n     * Note that these are only the rules that are specific to the map itself. They are not the rules that are actually used in a game instance instantiated\n     * from this resource world as these ones may be supplemented or overriden by other rules taken from the main config, dungeon config or the\n     * {@link de.erethon.dungeonsxl.api.dungeon.GameRule#DEFAULT_VALUES}.\n     *\n     * @return the {@link de.erethon.dungeonsxl.api.dungeon.GameRule}s of this world\n     */\n    GameRuleContainer getRules();\n\n    /**\n     * Returns the environment of the world as defined in the config or {@link org.bukkit.World.Environment#NORMAL} if nothing is set.\n     *\n     * @return the environment of the world as defined in the config or {@link org.bukkit.World.Environment#NORMAL} if nothing is set\n     */\n    Environment getWorldEnvironment();\n\n    /**\n     * Adds the player to the list of players that are invited to edit the resource.\n     *\n     * @param player the player\n     */\n    void addInvitedPlayer(OfflinePlayer player);\n\n    /**\n     * Removes a player from the list of players that are invited to edit the resource.\n     *\n     * @param player the player\n     * @return if the action was successful\n     */\n    boolean removeInvitedPlayer(OfflinePlayer player);\n\n    /**\n     * Returns if the player is invited to edit the resource.\n     *\n     * @param player the player\n     * @return if the player is invited to edit the resource\n     */\n    boolean isInvitedPlayer(OfflinePlayer player);\n\n    /**\n     * Creates a backup of the resource.\n     */\n    void backup();\n\n    /**\n     * Returns the loaded edit instance of this world or null if none exists.\n     *\n     * @return the loaded edit instance of this world or null if none exists\n     */\n    EditWorld getEditWorld();\n\n    /**\n     * Returns the loaded edit instance of this world or generates a new one if none exists.\n     *\n     * @param ignoreLimit if the instance limit set in the main config shall be ignored\n     * @return the loaded edit instance of this world or generates a new one if none exists\n     */\n    EditWorld getOrInstantiateEditWorld(boolean ignoreLimit);\n\n    /**\n     * Returns a new game instance of this resource.\n     *\n     * @see de.erethon.dungeonsxl.api.dungeon.Game#ensureWorldIsLoaded(boolean)\n     * @param game        the game the instance belongs to\n     * @param ignoreLimit if the instance limit set in the main config shall be ignored\n     * @return a new game instance of this resource\n     */\n    GameWorld instantiateGameWorld(Game game, boolean ignoreLimit);\n\n    /**\n     * Returns the single floor dungeon of this resource.\n     *\n     * @return the single floor dungeon of this resource\n     */\n    Dungeon getSingleFloorDungeon();\n\n}\n"
  },
  {
    "path": "build.bat",
    "content": "java -jar mvnbt.jar\npause\n"
  },
  {
    "path": "build.sh",
    "content": "#!/bin/bash\njava -jar mvnbt.jar\nread -rp \"Press any key to continue...\" -n 1\n"
  },
  {
    "path": "bukkit_blockdata/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-bukkit_blockdata</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-adapter</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-api</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>${spigotVersion.latest}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "bukkit_blockdata/src/main/java/de/erethon/dungeonsxl/adapter/block/BlockAdapterBlockData.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.adapter.block;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup.Color;\nimport org.bukkit.Axis;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.data.BlockData;\nimport org.bukkit.block.data.Directional;\nimport org.bukkit.block.data.Openable;\nimport org.bukkit.block.data.Orientable;\nimport org.bukkit.block.data.Rotatable;\nimport org.bukkit.block.data.type.Bed;\n\n/**\n * @author Daniel Saukel\n */\npublic class BlockAdapterBlockData implements BlockAdapter {\n\n    @Override\n    public boolean isBedHead(Block block) {\n        if (!(block.getBlockData() instanceof Bed)) {\n            throw new IllegalArgumentException(\"Block is not Bed\");\n        }\n        return ((Bed) block.getBlockData()).getPart() == Bed.Part.HEAD;\n    }\n\n    @Override\n    public void openDoor(Block block) {\n        if (!(block.getBlockData() instanceof Openable)) {\n            throw new IllegalArgumentException(\"Block is not Openable\");\n        }\n        Openable data = (Openable) block.getBlockData();\n        data.setOpen(true);\n        block.setBlockData(data);\n    }\n\n    @Override\n    public void closeDoor(Block block) {\n        if (!(block.getBlockData() instanceof Openable)) {\n            throw new IllegalArgumentException(\"Block is not Openable\");\n        }\n        Openable data = (Openable) block.getBlockData();\n        data.setOpen(false);\n        block.setBlockData(data);\n    }\n\n    @Override\n    public void setBlockWoolColor(Block block, Color color) {\n        block.setType(color.getWoolMaterial().getMaterial());\n    }\n\n    @Override\n    public BlockFace getFacing(Block block) {\n        if (block.getBlockData() instanceof Directional) {\n            return ((Directional) block.getBlockData()).getFacing();\n        } else if (block.getBlockData() instanceof Rotatable) {\n            return ((Rotatable) block.getBlockData()).getRotation();\n        } else {\n            throw new IllegalArgumentException(\"Block is not Directional or Rotatable\");\n        }\n    }\n\n    @Override\n    public void setFacing(Block block, BlockFace facing) {\n        BlockData data = block.getBlockData();\n        if (data instanceof Directional) {\n            ((Directional) data).setFacing(facing);\n        } else if (data instanceof Rotatable) {\n            ((Rotatable) data).setRotation(facing);\n        } else {\n            throw new IllegalArgumentException(\"Block is not Directional or Rotatable\");\n        }\n        block.setBlockData(data, false);\n    }\n\n    @Override\n    public void setAxis(Block block, boolean z) {\n        if (!(block.getBlockData() instanceof Orientable)) {\n            throw new IllegalArgumentException(\"Block is not Orientable\");\n        }\n        Orientable data = (Orientable) block.getBlockData();\n        data.setAxis(z ? Axis.Z : Axis.X);\n        block.setBlockData(data, false);\n    }\n\n}\n"
  },
  {
    "path": "bukkit_magicvalues/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-bukkit_magicvalues</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-adapter</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-api</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>1.12.2-R0.1-SNAPSHOT</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "bukkit_magicvalues/src/main/java/de/erethon/dungeonsxl/adapter/block/BlockAdapterMagicValues.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.adapter.block;\n\nimport de.erethon.dungeonsxl.api.player.PlayerGroup.Color;\nimport org.bukkit.Material;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.material.Bed;\nimport org.bukkit.material.Directional;\nimport org.bukkit.material.MaterialData;\n\n/**\n * @author Daniel Saukel\n */\npublic class BlockAdapterMagicValues implements BlockAdapter {\n\n    @Override\n    public boolean isBedHead(Block block) {\n        MaterialData data = block.getState().getData();\n        if (!(data instanceof Bed)) {\n            throw new IllegalArgumentException(\"Block is not Bed\");\n        }\n        return ((Bed) data).isHeadOfBed();\n    }\n\n    @Override\n    public void openDoor(Block block) {\n        block.setData((byte) (block.getData() + 4));\n    }\n\n    @Override\n    public void closeDoor(Block block) {\n        block.setData((byte) (block.getData() - 4));\n    }\n\n    @Override\n    public void setBlockWoolColor(Block block, Color color) {\n        block.setTypeIdAndData(Material.WOOL.getId(), color.getDyeColor().getWoolData(), false);\n    }\n\n    @Override\n    public BlockFace getFacing(Block block) {\n        MaterialData data = block.getState().getData();\n        if (!(data instanceof Directional)) {\n            throw new IllegalArgumentException(\"Block is not Directional\");\n        }\n        return ((Directional) data).getFacing();\n    }\n\n    @Override\n    public void setFacing(Block block, BlockFace facing) {\n        BlockState state = block.getState();\n        MaterialData data = state.getData();\n        if (!(data instanceof Directional)) {\n            throw new IllegalArgumentException(\"Block is not Directional\");\n        }\n        ((Directional) data).setFacingDirection(facing);\n        state.setData(data);\n        state.update();\n    }\n\n    @Override\n    public void setAxis(Block block, boolean z) {\n        block.setData(z ? (byte) 2 : 1);\n    }\n\n}\n"
  },
  {
    "path": "core/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-core</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <properties>\n        <dependencyVersion.bossshop>2.7.5</dependencyVersion.bossshop>\n        <dependencyVersion.citizens>2.0.26-SNAPSHOT</dependencyVersion.citizens>\n        <dependencyVersion.holographicdisplays>3.0.4</dependencyVersion.holographicdisplays>\n        <dependencyVersion.parties>3.2.16</dependencyVersion.parties>\n        <dependencyVersion.placeholderapi>2.12.2</dependencyVersion.placeholderapi>\n    </properties>\n    <build>\n        <resources>\n            <resource>\n                <targetPath>.</targetPath>\n                <filtering>true</filtering>\n                <directory>src/main/resources/</directory>\n                <includes>\n                    <include>plugin.yml</include>\n                    <include>languages/*</include>\n                </includes>\n            </resource>\n        </resources>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>properties-maven-plugin</artifactId>\n                <version>1.3.0</version>\n                <executions>\n                    <execution>\n                        <phase>generate-resources</phase>\n                        <goals>\n                            <goal>write-project-properties</goal>\n                        </goals>\n                        <configuration>\n                            <outputFile>${project.build.outputDirectory}/dxl.properties</outputFile>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-adapter</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-api</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-bukkit_blockdata</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-bukkit_magicvalues</artifactId>\n            <version>${project.parent.version}</version>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.spigotmc</groupId>\n            <artifactId>spigot-api</artifactId>\n            <version>${spigotVersion.latest}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.github.MilkBowl</groupId>\n            <artifactId>VaultAPI</artifactId>\n            <version>1.7</version>\n            <scope>provided</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.bukkit</groupId>\n                    <artifactId>bukkit</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>net.citizensnpcs</groupId>\n            <artifactId>citizens-main</artifactId>\n            <version>${dependencyVersion.citizens}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>me.filoghost.holographicdisplays</groupId>\n            <artifactId>holographicdisplays-legacy-api-v2</artifactId>\n            <version>${dependencyVersion.holographicdisplays}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.black_ixx</groupId>\n            <artifactId>BossShop</artifactId>\n            <version>${dependencyVersion.bossshop}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.griefcraft</groupId>\n            <artifactId>Modern-LWC</artifactId>\n            <version>2.1.2</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>me.clip</groupId>\n            <artifactId>placeholderapi</artifactId>\n            <version>${dependencyVersion.placeholderapi}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.alessiodp.parties</groupId>\n            <artifactId>parties-api</artifactId>\n            <version>${dependencyVersion.parties}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n    <repositories>\n        <repository>\n            <id>jitpack.io</id>\n            <url>https://jitpack.io</url>\n        </repository>\n        <repository>\n            <id>citizens-repo</id>\n            <url>https://repo.citizensnpcs.co/</url>\n        </repository>\n        <repository>\n            <id>placeholderapi</id>\n            <url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>\n        </repository>\n        <repository>\n            <id>codemc-repo</id>\n            <url>https://repo.codemc.org/repository/maven-public/</url>\n        </repository>\n        <repository>\n            <id>alessiodp-repo</id>\n            <url>https://repo.alessiodp.com/releases/</url>\n        </repository>\n    </repositories>\n</project>\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/DXLModule.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl;\n\nimport de.erethon.dungeonsxl.api.DungeonModule;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport static de.erethon.dungeonsxl.api.trigger.TriggerTypeKey.*;\nimport de.erethon.dungeonsxl.requirement.*;\nimport de.erethon.dungeonsxl.reward.*;\nimport de.erethon.dungeonsxl.sign.button.*;\nimport de.erethon.dungeonsxl.sign.passive.*;\nimport de.erethon.dungeonsxl.sign.rocker.*;\nimport de.erethon.dungeonsxl.sign.windup.*;\nimport de.erethon.dungeonsxl.trigger.*;\nimport de.erethon.xlib.util.Registry;\n\n/**\n * @author Daniel Saukel\n */\npublic class DXLModule implements DungeonModule {\n\n    @Override\n    public void initRequirements(Registry<String, Class<? extends Requirement>> requirementRegistry) {\n        requirementRegistry.add(\"feeLevel\", FeeLevelRequirement.class);\n        requirementRegistry.add(\"feeMoney\", FeeMoneyRequirement.class);\n        requirementRegistry.add(\"finishedDungeons\", FinishedDungeonsRequirement.class);\n        requirementRegistry.add(\"forbiddenItems\", ForbiddenItemsRequirement.class);\n        requirementRegistry.add(\"groupSize\", GroupSizeRequirement.class);\n        requirementRegistry.add(\"keyItems\", KeyItemsRequirement.class);\n        requirementRegistry.add(\"permission\", PermissionRequirement.class);\n        requirementRegistry.add(\"timeSinceFinish\", TimeSinceFinishRequirement.class);\n        requirementRegistry.add(\"timeSinceStart\", TimeSinceStartRequirement.class);\n        requirementRegistry.add(\"timeframe\", TimeframeRequirement.class);\n    }\n\n    @Override\n    public void initRewards(Registry<String, Class<? extends Reward>> rewardRegistry) {\n        rewardRegistry.add(\"item\", ItemReward.class);\n        rewardRegistry.add(\"money\", MoneyReward.class);\n        rewardRegistry.add(\"level\", LevelReward.class);\n    }\n\n    @Override\n    public void initSigns(Registry<String, Class<? extends DungeonSign>> signRegistry) {\n        signRegistry.add(\"ACTIONBAR\", ActionBarSign.class);\n        signRegistry.add(\"BED\", BedSign.class);\n        signRegistry.add(\"BLOCK\", BlockSign.class);\n        signRegistry.add(\"BOSSSHOP\", BossShopSign.class);\n        signRegistry.add(\"CHECKPOINT\", CheckpointSign.class);\n        signRegistry.add(\"CLASSES\", ClassesSign.class);\n        signRegistry.add(\"CMD\", CommandSign.class);\n        signRegistry.add(\"DROP\", DropSign.class);\n        signRegistry.add(\"DUNGEONCHEST\", DungeonChestSign.class);\n        signRegistry.add(\"END\", EndSign.class);\n        signRegistry.add(\"FLAG\", FlagSign.class);\n        signRegistry.add(\"HOLOGRAM\", HologramSign.class);\n        signRegistry.add(\"INTERACT\", InteractSign.class);\n        signRegistry.add(\"LEAVE\", LeaveSign.class);\n        signRegistry.add(\"LIVES\", LivesModifierSign.class);\n        signRegistry.add(\"LOBBY\", LobbySign.class);\n        signRegistry.add(\"MOB\", MobSign.class);\n        signRegistry.add(\"MSG\", ChatMessageSign.class);\n        signRegistry.add(\"NOTE\", NoteSign.class);\n        signRegistry.add(\"DOOR\", OpenDoorSign.class);\n        signRegistry.add(\"PLACE\", PlaceSign.class);\n        signRegistry.add(\"PROTECTION\", ProtectionSign.class);\n        signRegistry.add(\"READY\", ReadySign.class);\n        signRegistry.add(\"REDSTONE\", RedstoneSign.class);\n        signRegistry.add(\"RESOURCEPACK\", ResourcePackSign.class);\n        signRegistry.add(\"REWARDCHEST\", RewardChestSign.class);\n        signRegistry.add(\"SCRIPT\", ScriptSign.class);\n        signRegistry.add(\"SOUNDMSG\", SoundMessageSign.class);\n        signRegistry.add(\"START\", StartSign.class);\n        signRegistry.add(\"TELEPORT\", TeleportSign.class);\n        signRegistry.add(\"TITLE\", TitleSign.class);\n        signRegistry.add(\"TRIGGER\", TriggerSign.class);\n        signRegistry.add(\"WAVE\", WaveSign.class);\n    }\n\n    @Override\n    public void initGameRules(Registry<String, GameRule> gameRuleRegistry) {\n        for (GameRule rule : GameRule.VALUES) {\n            gameRuleRegistry.add(rule.getKey(), rule);\n        }\n    }\n\n    @Override\n    public void initTriggers(Registry<Character, Class<? extends Trigger>> triggerRegistry) {\n        triggerRegistry.add(DISTANCE, DistanceTrigger.class);\n        triggerRegistry.add(FORTUNE, FortuneTrigger.class);\n        triggerRegistry.add(INTERACT, InteractTrigger.class);\n        triggerRegistry.add(MOB, MobTrigger.class);\n        triggerRegistry.add(PRESENCE, PresenceTrigger.class);\n        //triggerRegistry.add(\"P\", ProgressTrigger.class);\n        triggerRegistry.add(REDSTONE, RedstoneTrigger.class);\n        triggerRegistry.add(GENERIC, SignTrigger.class);\n        triggerRegistry.add(USE_ITEM, UseItemTrigger.class);\n        triggerRegistry.add(WAVE, WaveTrigger.class);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl;\n\nimport de.erethon.dungeonsxl.adapter.block.BlockAdapter;\nimport de.erethon.dungeonsxl.adapter.block.BlockAdapterBlockData;\nimport de.erethon.dungeonsxl.adapter.block.BlockAdapterMagicValues;\nimport de.erethon.dungeonsxl.api.DungeonModule;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent;\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\nimport de.erethon.dungeonsxl.api.player.GroupAdapter;\nimport de.erethon.dungeonsxl.api.player.PlayerCache;\nimport de.erethon.dungeonsxl.api.player.PlayerClass;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.command.DCommandRegistry;\nimport de.erethon.dungeonsxl.config.MainConfig;\nimport de.erethon.dungeonsxl.config.MainConfig.BackupMode;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.global.GlobalProtectionCache;\nimport de.erethon.dungeonsxl.global.GlobalProtectionListener;\nimport de.erethon.dungeonsxl.mob.CitizensMobProvider;\nimport de.erethon.dungeonsxl.mob.CustomExternalMobProvider;\nimport de.erethon.dungeonsxl.mob.DMob;\nimport de.erethon.dungeonsxl.mob.DMobListener;\nimport de.erethon.dungeonsxl.mob.ExternalMobPlugin;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DInstancePlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.dungeonsxl.player.SecureModeTask;\nimport de.erethon.dungeonsxl.player.groupadapter.*;\nimport de.erethon.dungeonsxl.reward.RewardListener;\nimport de.erethon.dungeonsxl.sign.DSignListener;\nimport de.erethon.dungeonsxl.sign.button.EndSign;\nimport de.erethon.dungeonsxl.sign.passive.RewardChestSign;\nimport de.erethon.dungeonsxl.sign.passive.SignScript;\nimport de.erethon.dungeonsxl.sign.windup.CommandScript;\nimport de.erethon.dungeonsxl.sign.windup.MobSign;\nimport de.erethon.dungeonsxl.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.util.DependencyVersion;\nimport de.erethon.dungeonsxl.util.LWCUtil;\nimport de.erethon.dungeonsxl.util.PlaceholderUtil;\nimport de.erethon.dungeonsxl.world.DEditWorld;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.dungeonsxl.world.DWorldListener;\nimport de.erethon.dungeonsxl.world.LWCIntegration;\nimport de.erethon.dungeonsxl.world.WorldConfig;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.mob.ExMob;\nimport de.erethon.xlib.plugin.PluginInit;\nimport de.erethon.xlib.util.FileUtil;\nimport de.erethon.xlib.util.Registry;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport org.bukkit.Bukkit;\nimport org.bukkit.NamespacedKey;\nimport org.bukkit.World;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.HandlerList;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.meta.ItemMeta;\nimport org.bukkit.persistence.PersistentDataType;\nimport org.bukkit.plugin.java.JavaPlugin;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Tobias Schmitz, Daniel Saukel\n */\npublic class DungeonsXL extends JavaPlugin implements DungeonsAPI {\n\n    /* Plugin & lib instances */\n    private static DungeonsXL instance;\n    private XLib xlib;\n    private PluginInit init;\n\n    /* Util instances */\n    public static final BlockAdapter BLOCK_ADAPTER = Version.isAtLeast(Version.MC1_13) ? new BlockAdapterBlockData() : new BlockAdapterMagicValues();\n\n    /* Constants */\n    public static final String[] EXCLUDED_FILES = {\"config.yml\", \"uid.dat\", \"DXLData.data\", \"data\"};\n\n    /* Folders of internal features */\n    public static final File SIGNS = new File(SCRIPTS, \"signs\");\n    public static final File COMMANDS = new File(SCRIPTS, \"commands\");\n\n    /* Legacy */\n    public static final Map<String, Class<? extends DungeonSign>> LEGACY_SIGNS = new HashMap<>();\n\n    static {\n        LEGACY_SIGNS.put(\"CHEST\", RewardChestSign.class);\n        LEGACY_SIGNS.put(\"EXTERNALMOB\", MobSign.class);\n        LEGACY_SIGNS.put(\"FLOOR\", EndSign.class);\n    }\n\n    /* Caches & registries */\n    private Set<DungeonModule> modules = new HashSet<>();\n    private Collection<GroupAdapter> groupAdapters = new HashSet<>();\n    private PlayerCache playerCache;\n    private Collection<Game> gameCache;\n    private Registry<String, PlayerClass> classRegistry;\n    private Registry<String, Class<? extends DungeonSign>> signRegistry;\n    private Registry<String, Class<? extends Requirement>> requirementRegistry;\n    private Registry<String, Class<? extends Reward>> rewardRegistry;\n    private Registry<String, Dungeon> dungeonRegistry;\n    private Registry<String, ResourceWorld> mapRegistry;\n    private Registry<Integer, InstanceWorld> instanceCache;\n    private Registry<String, GameRule> gameRuleRegistry;\n    private Registry<Character, Class<? extends Trigger>> triggerRegistry;\n    private Registry<String, ExternalMobProvider> externalMobProviderRegistry;\n    private Registry<String, PlayerGroup> playerGroupCache;\n\n    @Deprecated\n    private class SignRegistry extends Registry<String, Class<? extends DungeonSign>> {\n\n        @Override\n        public Class<? extends DungeonSign> get(String key) {\n            Class<? extends DungeonSign> clss = super.get(key);\n            if (clss == null) {\n                return LEGACY_SIGNS.get(key);\n            }\n            return clss;\n        }\n    }\n\n    private class GameRuleRegistry extends Registry<String, GameRule> {\n\n        @Override\n        public void add(String key, GameRule rule) {\n            super.add(key, rule);\n            if (loaded) {\n                GameRule.DEFAULT_VALUES.setState(rule, rule.getDefaultValue());\n                mainConfig.getDefaultWorldConfig().updateGameRule(rule);\n                for (Dungeon apiDungeon : dungeonRegistry) {\n                    DDungeon dungeon = ((DDungeon) apiDungeon);\n                    if (dungeon.isMultiFloor()) {\n                        dungeon.getConfig().getDefaultValues().updateGameRule(rule);\n                        dungeon.getConfig().getOverrideValues().updateGameRule(rule);\n                    } else {\n                        WorldConfig cfg = ((DResourceWorld) dungeon.getMap()).getConfig(false);\n                        cfg.updateGameRule(rule);\n                    }\n                }\n                dungeonRegistry.forEach(Dungeon::setupRules);\n            }\n        }\n\n    }\n\n    private class PlayerGroupCache extends Registry<String, PlayerGroup> {\n\n        @Override\n        public PlayerGroup get(String key) {\n            PlayerGroup group = elements.get(key);\n            if (group != null) {\n                return group;\n            }\n            for (PlayerGroup value : elements.values()) {\n                if (((DGroup) value).getUntaggedName().equalsIgnoreCase(key)) {\n                    return value;\n                }\n            }\n            return null;\n        }\n\n    }\n\n    /* Global state variables */\n    private boolean loaded, loadingWorld;\n\n    private MainConfig mainConfig;\n\n    /* Caches & registries of internal features */\n    private GlobalProtectionCache protections;\n    private Registry<String, SignScript> signScriptRegistry;\n    private Registry<String, CommandScript> commandScriptRegistry;\n\n    @Override\n    public void onEnable() {\n        if (!DependencyVersion.XLIB.check()) {\n            getLogger().log(Level.SEVERE, \"DungeonsXL requires ItemsXL v{0} or higher to run.\", DependencyVersion.XLIB);\n            getServer().getPluginManager().disablePlugin(this);\n            return;\n        }\n\n        instance = this;\n        xlib = XLib.getInstance();\n        init = new PluginInit(this, xlib, DependencyVersion.META);\n        initFolders();\n        DPermission.register();\n        registerModule(new DXLModule());\n        init();\n        checkState();\n        if (getServer().getPluginManager().isPluginEnabled(\"PlaceholderAPI\")) {\n            new PlaceholderUtil(this, \"dxl\").register();\n        }\n        if (getServer().getPluginManager().isPluginEnabled(\"Parties\")) {\n            registerGroupAdapter(new PartiesAdapter(this));\n        }\n        init.init(new DCommandRegistry(this, init), mainConfig.isUpdaterEnabled());\n        loaded = true;\n    }\n\n    @Override\n    public void onDisable() {\n        if (!loaded) {\n            return;\n        }\n        loaded = false;\n        saveData();\n        deleteAllInstances();\n        HandlerList.unregisterAll(this);\n        getServer().getScheduler().cancelTasks(this);\n        DPermission.unregister();\n    }\n\n    public void initFolders() {\n        if (!getDataFolder().exists()) {\n            getDataFolder().mkdir();\n        }\n        BACKUPS.mkdir();\n        MAPS.mkdir();\n        PLAYERS.mkdir();\n        SCRIPTS.mkdir();\n        CLASSES.mkdir();\n        DUNGEONS.mkdir();\n        SIGNS.mkdir();\n        COMMANDS.mkdir();\n    }\n\n    public void reload() {\n        /* Add default values */\n        requirementRegistry = new Registry<>();\n        modules.forEach(m -> m.initRequirements(requirementRegistry));\n\n        rewardRegistry = new Registry<>();\n        modules.forEach(m -> m.initRewards(rewardRegistry));\n\n        signRegistry = new SignRegistry();\n        modules.forEach(m -> m.initSigns(signRegistry));\n\n        gameRuleRegistry = new GameRuleRegistry();\n        modules.forEach(m -> m.initGameRules(gameRuleRegistry));\n\n        triggerRegistry = new Registry<>();\n        modules.forEach(m -> m.initTriggers(triggerRegistry));\n\n        mainConfig = new MainConfig(this, new File(getDataFolder(), \"config.yml\"));\n\n        /* Maps & dungeons */\n        // Maps\n        mapRegistry = new Registry<>();\n        for (File file : MAPS.listFiles()) {\n            if (file.isDirectory() && !file.getName().equals(\".raw\")) {\n                mapRegistry.add(file.getName(), new DResourceWorld(this, file));\n            }\n        }\n        // Dungeons - Map dungeons\n        dungeonRegistry = new Registry<>();\n        for (ResourceWorld resource : mapRegistry) {\n            dungeonRegistry.add(resource.getName(), new DDungeon(this, resource));\n        }\n        // Dungeons - Linked dungeons\n        if (init.isXLDevMode()) {\n            for (File file : DUNGEONS.listFiles()) {\n                Dungeon dungeon = DDungeon.create(this, file);\n\n                if (dungeon != null) {\n                    dungeonRegistry.add(dungeon.getName(), dungeon);\n                } else {\n                    MessageUtil.log(this, \"&4The setup of dungeon &6\" + file.getName()\n                            + \"&4 is incorrect. See https://github.com/DRE2N/DungeonsXL/wiki/dungeon-configuration for reference.\");\n                }\n            }\n        } else if (DUNGEONS.listFiles().length != 0) {\n            MessageUtil.log(this, \"&4Multi floor dungeons are not part of the range of functions of this build.\");\n        }\n        // Raw map to copy\n        if (!DResourceWorld.RAW.exists()) {\n            DResourceWorld.createRaw();\n        }\n\n        /* Scripts & global data */\n        classRegistry = new Registry<>();\n        for (File script : FileUtil.getFilesForFolder(CLASSES)) {\n            PlayerClass clss = new PlayerClass(xlib, script);\n            classRegistry.add(clss.getName(), clss);\n        }\n        signScriptRegistry = new Registry<>();\n        for (File script : FileUtil.getFilesForFolder(SIGNS)) {\n            SignScript sign = new SignScript(script);\n            signScriptRegistry.add(sign.getName(), sign);\n        }\n        commandScriptRegistry = new Registry<>();\n        for (File script : FileUtil.getFilesForFolder(COMMANDS)) {\n            CommandScript cmd = new CommandScript(script);\n            commandScriptRegistry.add(cmd.getName(), cmd);\n        }\n        protections = new GlobalProtectionCache(this);\n        protections.loadAll();\n\n        /* Integrations */\n        if (LWCUtil.isLWCLoaded()) {\n            new LWCIntegration(this);\n        }\n        // Mobs - Supported providers\n        externalMobProviderRegistry = new Registry<>();\n        for (ExternalMobPlugin externalMobPlugin : ExternalMobPlugin.values()) {\n            externalMobProviderRegistry.add(externalMobPlugin.getIdentifier(), externalMobPlugin);\n        }\n        if (getServer().getPluginManager().getPlugin(\"Citizens\") != null) {\n            CitizensMobProvider citizensMobProvider = new CitizensMobProvider(this);\n            externalMobProviderRegistry.add(\"CI\", citizensMobProvider);\n            getServer().getPluginManager().registerEvents(citizensMobProvider, this);\n        } else {\n            MessageUtil.log(this, \"Could not find compatible Citizens plugin. The mob provider Citizens (\\\"CI\\\") will not get enabled...\");\n        }\n        // Mobs - Custom providers\n        for (Entry<String, Object> customExternalMobProvider : mainConfig.getExternalMobProviders().entrySet()) {\n            externalMobProviderRegistry.add(customExternalMobProvider.getKey(), new CustomExternalMobProvider(customExternalMobProvider));\n        }\n\n        /* Players */\n        if (mainConfig.isSecureModeEnabled()) {\n            new SecureModeTask(this).runTaskTimer(this, mainConfig.getSecureModeCheckInterval(), mainConfig.getSecureModeCheckInterval());\n        }\n        playerCache = new PlayerCache();\n        playerGroupCache = new PlayerGroupCache();\n\n        gameCache = new ArrayList<>();\n        instanceCache = new Registry<>();\n    }\n\n    public void init() {\n        reload();\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                playerCache.getAllInstancePlayers().forEach(p -> ((DInstancePlayer) p).update());\n            }\n        }.runTaskTimer(this, 2L, 2L);\n\n        /* Initialize listeners */\n        getServer().getPluginManager().registerEvents(new DWorldListener(this), this);\n        getServer().getPluginManager().registerEvents(new GlobalProtectionListener(this), this);\n        getServer().getPluginManager().registerEvents(new RewardListener(this), this);\n        getServer().getPluginManager().registerEvents(new TriggerListener(this), this);\n        getServer().getPluginManager().registerEvents(new DSignListener(this), this);\n        getServer().getPluginManager().registerEvents(new DMobListener(this), this);\n        getServer().getPluginManager().registerEvents(new DPlayerListener(this), this);\n    }\n\n    public void saveData() {\n        protections.saveAll();\n        instanceCache.getAllIf(i -> i instanceof EditWorld).forEach(i -> ((DEditWorld) i).forceSave());\n    }\n\n    public void checkState() {\n        Bukkit.getOnlinePlayers().forEach(p -> new DGlobalPlayer(this, p));\n\n        for (File file : Bukkit.getWorldContainer().listFiles()) {\n            if (!file.getName().startsWith(\"DXL_\") || !file.isDirectory()) {\n                continue;\n            }\n\n            if (file.getName().startsWith(\"DXL_Edit_\")) {\n                for (File mapFile : file.listFiles()) {\n                    if (!mapFile.getName().startsWith(\".id_\")) {\n                        continue;\n                    }\n\n                    String name = mapFile.getName().substring(4);\n\n                    File resource = new File(DungeonsXL.MAPS, name);\n                    File backup = new File(DungeonsXL.BACKUPS, resource.getName() + \"-\" + System.currentTimeMillis() + \"_crashbackup\");\n                    FileUtil.copyDir(resource, backup);\n                    // Remove all files from the backupped resource world but not the config & data that we cannot fetch from the instance.\n                    remove:\n                    for (File remove : FileUtil.getFilesForFolder(resource)) {\n                        for (String nope : DungeonsXL.EXCLUDED_FILES) {\n                            if (remove.getName().equals(nope)) {\n                                continue remove;\n                            }\n                        }\n                        remove.delete();\n                    }\n                    DResourceWorld.deleteUnusedFiles(file);\n                    FileUtil.copyDir(file, resource, DungeonsXL.EXCLUDED_FILES);\n                }\n            }\n\n            FileUtil.removeDir(file);\n        }\n    }\n\n    /* Getters and loaders */\n    /**\n     * @return the plugin instance\n     */\n    public static DungeonsXL getInstance() {\n        return instance;\n    }\n\n    public PluginInit getInitializer() {\n        return init;\n    }\n\n    @Override\n    public XLib getXLib() {\n        return xlib;\n    }\n\n    @Override\n    public PlayerCache getPlayerCache() {\n        return playerCache;\n    }\n\n    @Override\n    public Collection<Game> getGameCache() {\n        return gameCache;\n    }\n\n    @Override\n    public Registry<String, PlayerClass> getClassRegistry() {\n        return classRegistry;\n    }\n\n    @Override\n    public Registry<String, Class<? extends DungeonSign>> getSignRegistry() {\n        return signRegistry;\n    }\n\n    @Override\n    public Registry<String, Class<? extends Requirement>> getRequirementRegistry() {\n        return requirementRegistry;\n    }\n\n    @Override\n    public Registry<String, Class<? extends Reward>> getRewardRegistry() {\n        return rewardRegistry;\n    }\n\n    @Override\n    public Registry<String, Dungeon> getDungeonRegistry() {\n        return dungeonRegistry;\n    }\n\n    @Override\n    public Registry<String, ResourceWorld> getMapRegistry() {\n        return mapRegistry;\n    }\n\n    @Override\n    public Registry<Integer, InstanceWorld> getInstanceCache() {\n        return instanceCache;\n    }\n\n    @Override\n    public Registry<String, GameRule> getGameRuleRegistry() {\n        return gameRuleRegistry;\n    }\n\n    @Override\n    public Registry<Character, Class<? extends Trigger>> getTriggerRegistry() {\n        return triggerRegistry;\n    }\n\n    @Override\n    public Registry<String, ExternalMobProvider> getExternalMobProviderRegistry() {\n        return externalMobProviderRegistry;\n    }\n\n    @Override\n    public Registry<String, PlayerGroup> getGroupCache() {\n        return playerGroupCache;\n    }\n\n    @Override\n    public void registerModule(DungeonModule module) {\n        modules.add(module);\n    }\n\n    @Override\n    public void registerGroupAdapter(GroupAdapter groupAdapter) {\n        if (mainConfig.areGroupAdaptersEnabled()) {\n            groupAdapters.add(groupAdapter);\n        } else {\n            MessageUtil.log(this, \"&4The group adapter &6\" + groupAdapter.getClass().getName() + \" &4was not registered because the feature is disabled.\");\n        }\n    }\n\n    /**\n     * Returns a collection of the loadedGroupAdapters\n     *\n     * @return a collection of GroupAdapters\n     */\n    public Collection<GroupAdapter> getGroupAdapters() {\n        return groupAdapters;\n    }\n\n    /**\n     * Returns true if the plugin is not currently in the process of enabling or disabling or entirely disabled, otherwise false.\n     *\n     * @return true if the plugin is not currently in the process of enabling or disabling or entirely disabled, otherwise false\n     */\n    public boolean isLoaded() {\n        return loaded;\n    }\n\n    /**\n     * Returns true if the plugin is currently loading a world, false if not.\n     * <p>\n     * If the plugin is loading a world, it is locked in order to prevent loading two at once.\n     *\n     * @return true if the plugin is currently loading a world, false if not\n     */\n    public boolean isLoadingWorld() {\n        return loadingWorld;\n    }\n\n    /**\n     * Notifies the plugin that a world is being loaded.\n     * <p>\n     * If the plugin is loading a world, it is locked in order to prevent loading two at once.\n     *\n     * @param loadingWorld if a world is being loaded\n     */\n    public void setLoadingWorld(boolean loadingWorld) {\n        MessageUtil.debug(this, \"World loading is now \" + (loadingWorld ? \"LOCKED\" : \"UNLOCKED\"));\n        this.loadingWorld = loadingWorld;\n    }\n\n    /**\n     * Returns the command registry.\n     *\n     * @return the command registry\n     */\n    public DCommandRegistry getCommandRegistry() {\n        return (DCommandRegistry) init.getCommandRegistry();\n    }\n\n    /**\n     * @return the loaded instance of MainConfig\n     */\n    public MainConfig getMainConfig() {\n        return mainConfig;\n    }\n\n    /**\n     * @return the loaded instance of GlobalProtectionCache\n     */\n    public GlobalProtectionCache getGlobalProtectionCache() {\n        return protections;\n    }\n\n    /**\n     * Returns a registry of the loaded sign scripts.\n     *\n     * @return a registry of the loaded sign scripts\n     */\n    public Registry<String, SignScript> getSignScriptRegistry() {\n        return signScriptRegistry;\n    }\n\n    /**\n     * Returns a registry of the loaded command scripts.\n     *\n     * @return a registry of the loaded command scripts\n     */\n    public Registry<String, CommandScript> getCommandScriptRegistry() {\n        return commandScriptRegistry;\n    }\n\n    /* Object initialization */\n    @Override\n    public PlayerGroup createGroup(Player leader) {\n        return DGroup.create(this, GroupCreateEvent.Cause.CUSTOM, leader, null, null, null);\n    }\n\n    @Override\n    public PlayerGroup createGroup(Player leader, PlayerGroup.Color color) {\n        return DGroup.create(this, GroupCreateEvent.Cause.CUSTOM, leader, null, color, null);\n    }\n\n    @Override\n    public PlayerGroup createGroup(Player leader, String name) {\n        return DGroup.create(this, GroupCreateEvent.Cause.CUSTOM, leader, name, null, null);\n    }\n\n    @Override\n    public PlayerGroup createGroup(Player leader, Dungeon dungeon) {\n        return DGroup.create(this, GroupCreateEvent.Cause.CUSTOM, leader, null, null, dungeon);\n    }\n\n    @Override\n    public PlayerGroup createGroup(Player leader, Collection<Player> members, String name, Dungeon dungeon) {\n        PlayerGroup group = DGroup.create(this, GroupCreateEvent.Cause.CUSTOM, leader, name, null, dungeon);\n        if (members != null) {\n            members.forEach(group::addMember);\n        }\n        return group;\n    }\n\n    @Override\n    public DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, String triggerId) {\n        DungeonMob mob = getDungeonMob(entity);\n        if (mob != null) {\n            return mob;\n        } else {\n            return new DMob(entity, gameWorld, xlib.getExMob(triggerId), triggerId);\n        }\n    }\n\n    @Override\n    public DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, ExMob type) {\n        DungeonMob mob = getDungeonMob(entity);\n        if (mob != null) {\n            return mob;\n        } else {\n            return new DMob(entity, gameWorld, type, type.getId());\n        }\n    }\n\n    @Override\n    public DungeonMob wrapEntity(LivingEntity entity, GameWorld gameWorld, ExMob type, String triggerId) {\n        DungeonMob mob = getDungeonMob(entity);\n        if (mob != null) {\n            return mob;\n        } else {\n            return new DMob(entity, gameWorld, type, triggerId);\n        }\n    }\n\n    /* Getters */\n    @Override\n    public DungeonMob getDungeonMob(LivingEntity entity) {\n        GameWorld gameWorld = getGameWorld(entity.getWorld());\n        if (gameWorld == null) {\n            return null;\n        }\n        for (DungeonMob mob : gameWorld.getMobs()) {\n            if (mob.getEntity() == entity) {\n                return mob;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public PlayerGroup getPlayerGroup(Player member) {\n        for (PlayerGroup group : playerGroupCache) {\n            if (group.getMembers().contains(member)) {\n                return group;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public Game getGame(Player player) {\n        for (Game game : gameCache) {\n            if (game.getPlayers().contains(player)) {\n                return game;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public Game getGame(World world) {\n        GameWorld gameWorld = getGameWorld(world);\n        return gameWorld != null ? gameWorld.getGame() : null;\n    }\n\n    @Override\n    public GameWorld getGameWorld(World world) {\n        InstanceWorld instance = getInstanceWorld(world);\n        return instance instanceof GameWorld ? (GameWorld) instance : null;\n    }\n\n    @Override\n    public EditWorld getEditWorld(World world) {\n        InstanceWorld instance = getInstanceWorld(world);\n        return instance instanceof EditWorld ? (EditWorld) instance : null;\n    }\n\n    public InstanceWorld getInstanceWorld(World world) {\n        for (InstanceWorld instance : instanceCache) {\n            if (world.equals(instance.getWorld())) {\n                return instance;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean isInstance(World world) {\n        return world.getName().startsWith(\"DXL_Game_\") || world.getName().startsWith(\"DXL_Edit_\");\n    }\n\n    @Override\n    public boolean isDungeonItem(ItemStack itemStack) {\n        if (!Version.isAtLeast(Version.MC1_16_5)) {\n            return false;\n        }\n        if (itemStack == null || !itemStack.hasItemMeta()) {\n            return false;\n        }\n        return itemStack.getItemMeta().getPersistentDataContainer().has(NamespacedKey.fromString(\"dungeon_item\", this), PersistentDataType.BYTE);\n    }\n\n    @Override\n    public ItemStack setDungeonItem(ItemStack itemStack, boolean dungeonItem) {\n        if (!Version.isAtLeast(Version.MC1_16_5)) {\n            return null;\n        }\n        if (itemStack == null || itemStack.getItemMeta() == null) {\n            return null;\n        }\n        ItemStack dIStack = itemStack.clone();\n        ItemMeta meta = dIStack.getItemMeta();\n        NamespacedKey key = NamespacedKey.fromString(\"dungeon_item\", this);\n        if (dungeonItem) {\n            meta.getPersistentDataContainer().set(key, PersistentDataType.BYTE, (byte) 1);\n        } else {\n            meta.getPersistentDataContainer().remove(key);\n        }\n        dIStack.setItemMeta(meta);\n        return dIStack;\n    }\n\n    /**\n     * Clean up all instances.\n     */\n    public void deleteAllInstances() {\n        BackupMode backupMode = mainConfig.getBackupMode();\n        for (InstanceWorld instance : instanceCache.getAll()) {\n            if (backupMode == BackupMode.ON_DISABLE | backupMode == BackupMode.ON_DISABLE_AND_SAVE && instance instanceof EditWorld) {\n                instance.getResource().backup();\n            }\n\n            instance.delete();\n        }\n    }\n\n    /**\n     * Checks if an old player wrapper instance of the user exists. If yes, the old Player of the user is replaced with the new object.\n     *\n     * @param player the player to check\n     * @return if the player exists\n     */\n    public boolean checkPlayer(Player player) {\n        DGamePlayer dPlayer = (DGamePlayer) playerCache.getFirstGamePlayerIf(p -> p.getUniqueId().equals(player.getUniqueId()));\n        if (dPlayer == null) {\n            return false;\n        }\n\n        dPlayer.setPlayer(player);\n        playerCache.remove(dPlayer);\n        playerCache.add(player, dPlayer);\n        dPlayer.setOfflineTimeMillis(0);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/BreakCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class BreakCommand extends DCommand {\n\n    public BreakCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"break\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_BREAK_HELP.getMessage());\n        setPermission(DPermission.BREAK.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer dGlobalPlayer = dPlayers.get(player);\n\n        if (dGlobalPlayer.isInBreakMode()) {\n            dGlobalPlayer.setInBreakMode(false);\n            MessageUtil.sendMessage(sender, DMessage.CMD_BREAK_PROTECTED_MODE.getMessage());\n\n        } else {\n            dGlobalPlayer.setInBreakMode(true);\n            MessageUtil.sendMessage(sender, DMessage.CMD_BREAK_BREAK_MODE.getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ChatCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DEditPlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ChatCommand extends DCommand {\n\n    public ChatCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"chat\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_CHAT_HELP.getMessage());\n        setPermission(DPermission.CHAT.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer dPlayer = dPlayers.get(player);\n\n        if (plugin.getPlayerGroup(player) == null && !(dPlayer instanceof DEditPlayer)) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        dPlayer.setInGroupChat(!dPlayer.isInGroupChat());\n        MessageUtil.sendMessage(player, (dPlayer.isInGroupChat() ? DMessage.CMD_CHAT_DUNGEON_CHAT : DMessage.CMD_CHAT_NORMAL_CHAT).getMessage());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ChatSpyCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ChatSpyCommand extends DCommand {\n\n    public ChatSpyCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"chatSpy\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_CHATSPY_HELP.getMessage());\n        setPermission(DPermission.CHAT_SPY.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer dPlayer = dPlayers.get(player);\n\n        dPlayer.setInChatSpyMode(!dPlayer.isInChatSpyMode());\n        MessageUtil.sendMessage(player, (dPlayer.isInChatSpyMode() ? DMessage.CMD_CHATSPY_STARTED : DMessage.CMD_CHATSPY_STOPPED).getMessage());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/CreateCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.player.DEditPlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.world.DEditWorld;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.io.File;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.command.ConsoleCommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class CreateCommand extends DCommand {\n\n    public CreateCommand(DungeonsXL plugin) {\n        super(plugin);\n        setMinArgs(1);\n        setMaxArgs(1);\n        setCommand(\"create\");\n        setHelp(DMessage.CMD_CREATE_HELP.getMessage());\n        setPermission(DPermission.CREATE.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        String name = args[1];\n\n        if (new File(DungeonsXL.MAPS, name).exists()) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NAME_IN_USE.getMessage(name));\n            return;\n        }\n\n        if (name.length() > 15) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NAME_TOO_LONG.getMessage());\n            return;\n        }\n\n        if (sender instanceof ConsoleCommandSender) {\n            MessageUtil.log(plugin, \"&6Creating new map.\");\n            MessageUtil.log(plugin, \"&6Generating new world...\");\n\n            DResourceWorld resource = new DResourceWorld(plugin, name);\n            plugin.getMapRegistry().add(name, resource);\n            DEditWorld editWorld = resource.generate();\n            editWorld.save();\n            editWorld.delete();\n\n            MessageUtil.log(plugin, \"&6World generation finished.\");\n\n        } else if (sender instanceof Player) {\n            Player player = (Player) sender;\n\n            if (dPlayers.getGamePlayer(player) != null) {\n                MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n                return;\n            }\n\n            MessageUtil.log(plugin, \"&6Creating new map.\");\n            MessageUtil.log(plugin, \"&6Generating new world...\");\n\n            DResourceWorld resource = new DResourceWorld(plugin, name);\n            plugin.getMapRegistry().add(name, resource);\n            plugin.getDungeonRegistry().add(name, new DDungeon(plugin, resource));\n            DEditWorld editWorld = resource.generate();\n\n            MessageUtil.log(plugin, \"&6World generation finished.\");\n\n            new DEditPlayer(plugin, player, editWorld);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/DCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.PlayerCache;\nimport de.erethon.dungeonsxl.config.MainConfig;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.command.DRECommand;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class DCommand extends DRECommand {\n\n    protected DungeonsXL plugin;\n    protected XLib xlib;\n    protected MainConfig config;\n    protected PlayerCache dPlayers;\n\n    protected DCommand(DungeonsXL plugin) {\n        this.plugin = plugin;\n        xlib = plugin.getXLib();\n        config = plugin.getMainConfig();\n        dPlayers = plugin.getPlayerCache();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/DCommandRegistry.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.xlib.command.DRECommandRegistry;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.plugin.PluginInit;\n\n/**\n * An enumeration of all command instances.\n *\n * @author Daniel Saukel\n */\npublic class DCommandRegistry extends DRECommandRegistry {\n\n    public static final String LABEL = \"dungeonsxl\";\n\n    public BreakCommand breakCmd;\n    public ChatCommand chat;\n    public ChatSpyCommand chatSpy;\n    public CreateCommand create;\n    public EditCommand edit;\n    public EnterCommand enter;\n    public EscapeCommand escape;\n    public DeleteCommand delete;\n    public DungeonItemCommand dungeonItem;\n    public GameCommand game;\n    public GroupCommand group;\n    public HelpCommand help;\n    public ImportCommand importCmd;\n    public InviteCommand invite;\n    public KickCommand kick;\n    public LeaveCommand leave;\n    public ListCommand list;\n    public LivesCommand lives;\n    public MainCommand main;\n    public MsgCommand message;\n    public PlayCommand play;\n    public PortalCommand portal;\n    public ReloadCommand reload;\n    public RenameCommand rename;\n    public ResourcePackCommand resourcePack;\n    public SaveCommand save;\n    public StatusCommand status;\n    public TestCommand test;\n    public UninviteCommand uninvite;\n\n    public DCommandRegistry(DungeonsXL plugin, PluginInit init) {\n        super(LABEL, init);\n\n        breakCmd = new BreakCommand(plugin);\n        chat = new ChatCommand(plugin);\n        chatSpy = new ChatSpyCommand(plugin);\n        create = new CreateCommand(plugin);\n        edit = new EditCommand(plugin);\n        enter = new EnterCommand(plugin);\n        escape = new EscapeCommand(plugin);\n        delete = new DeleteCommand(plugin);\n        dungeonItem = new DungeonItemCommand(plugin);\n        game = new GameCommand(plugin);\n        group = new GroupCommand(plugin);\n        help = new HelpCommand(plugin);\n        importCmd = new ImportCommand(plugin);\n        invite = new InviteCommand(plugin);\n        kick = new KickCommand(plugin);\n        leave = new LeaveCommand(plugin);\n        list = new ListCommand(plugin);\n        lives = new LivesCommand(plugin);\n        main = new MainCommand(plugin);\n        message = new MsgCommand(plugin);\n        play = new PlayCommand(plugin);\n        portal = new PortalCommand(plugin);\n        reload = new ReloadCommand(plugin);\n        rename = new RenameCommand(plugin);\n        resourcePack = new ResourcePackCommand(plugin);\n        save = new SaveCommand(plugin);\n        status = new StatusCommand(plugin);\n        test = new TestCommand(plugin);\n        uninvite = new UninviteCommand(plugin);\n\n        addCommand(breakCmd);\n        addCommand(create);\n        addCommand(delete);\n        if (Version.isAtLeast(Version.MC1_16_5)) {\n            addCommand(dungeonItem);\n        }\n        addCommand(edit);\n        addCommand(enter);\n        addCommand(escape);\n        addCommand(game);\n        addCommand(group);\n        addCommand(help);\n        addCommand(importCmd);\n        addCommand(invite);\n        addCommand(kick);\n        addCommand(leave);\n        addCommand(list);\n        addCommand(lives);\n        addCommand(main);\n        addCommand(message);\n        addCommand(play);\n        addCommand(portal);\n        addCommand(reload);\n        addCommand(rename);\n        addCommand(resourcePack);\n        addCommand(save);\n        addCommand(status);\n        addCommand(test);\n        addCommand(uninvite);\n        if (plugin.getMainConfig().isChatEnabled()) {\n            addCommand(chat);\n            addCommand(chatSpy);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/DeleteCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.dungeon.DungeonConfig;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.FileUtil;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.TextComponent;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class DeleteCommand extends DCommand {\n\n    public DeleteCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"delete\");\n        setMinArgs(1);\n        setMaxArgs(2);\n        setHelp(DMessage.CMD_DELETE_HELP.getMessage());\n        setPermission(DPermission.DELETE.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        ResourceWorld resource = plugin.getMapRegistry().get(args[1]);\n        if (resource == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[1]));\n            return;\n        }\n\n        if (args.length == 2 && sender instanceof Player) {\n            ClickEvent onClickConfirm = new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/dungeonsxl delete \" + args[1] + \" true\");\n            TextComponent confirm = new TextComponent(DMessage.BUTTON_ACCEPT.getMessage());\n            confirm.setClickEvent(onClickConfirm);\n\n            ClickEvent onClickDeny = new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/dungeonsxl delete \" + args[1] + \" false\");\n            TextComponent deny = new TextComponent(DMessage.BUTTON_DENY.getMessage());\n            deny.setClickEvent(onClickDeny);\n\n            MessageUtil.sendMessage(sender, DMessage.CMD_DELETE_BACKUPS.getMessage());\n            ((Player) sender).spigot().sendMessage(confirm, new TextComponent(\" \"), deny);\n\n            return;\n        }\n\n        if (resource.getEditWorld() != null) {\n            resource.getEditWorld().delete(false);\n        }\n        plugin.getMapRegistry().remove(resource);\n        FileUtil.removeDir(resource.getFolder());\n\n        if (args[2].equalsIgnoreCase(\"true\")) {\n            for (File file : DungeonsXL.BACKUPS.listFiles()) {\n                if (file.getName().startsWith(resource.getName() + \"-\")) {\n                    FileUtil.removeDir(file);\n                }\n            }\n        }\n\n        List<Dungeon> toRemove = new ArrayList<>();\n        for (Dungeon dungeon : plugin.getDungeonRegistry()) {\n            if (dungeon.getStartFloor().equals(resource)) {\n                toRemove.add(dungeon);\n                if (dungeon.isMultiFloor()) {\n                    ((DDungeon) dungeon).getConfig().getFile().delete();\n                }\n            } else if (dungeon.isMultiFloor() && dungeon.getEndFloor().equals(resource)) {\n                toRemove.add(dungeon);\n                ((DDungeon) dungeon).getConfig().getFile().delete();\n            } else if (dungeon.isMultiFloor() && dungeon.getFloors().contains(resource)) {\n                dungeon.removeFloor(resource);\n                DungeonConfig config = ((DDungeon) dungeon).getConfig();\n                List<String> floors = config.getConfig().getStringList(\"floors\");\n                floors.remove(resource.getName());\n                config.getConfig().set(\"floors\", floors);\n                config.save();\n            }\n        }\n        toRemove.forEach(plugin.getDungeonRegistry()::remove);\n\n        MessageUtil.sendMessage(sender, DMessage.CMD_DELETE_SUCCESS.getMessage(args[1]));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/DungeonItemCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.PlayerInventory;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DungeonItemCommand extends DCommand {\n\n    public DungeonItemCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"dungeonItem\");\n        setAliases(\"di\");\n        setMinArgs(0);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_DUNGEON_ITEM_HELP.getMessage());\n        setPermission(DPermission.DUNGEON_ITEM.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(false);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        PlayerInventory inv = player.getInventory();\n\n        ItemStack itemStack = inv.getItemInMainHand();\n        if (itemStack.getType().isAir()) {\n            MessageUtil.sendTitleMessage(player, DMessage.ERROR_NO_ITEM_IN_MAIN_HAND.getMessage());\n            return;\n        }\n\n        String action = args.length >= 2 ? args[1] : \"info\";\n        if (action.equalsIgnoreCase(\"true\")) {\n            inv.setItemInMainHand(plugin.setDungeonItem(itemStack, true));\n            MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_SET_DUNGEON.getMessage());\n            MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_DUNGEON_ITEM_HELP.getMessage());\n\n        } else if (action.equalsIgnoreCase(\"false\")) {\n            inv.setItemInMainHand(plugin.setDungeonItem(itemStack, false));\n            MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_SET_GLOBAL.getMessage());\n            MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_GLOBAL_ITEM_HELP.getMessage());\n\n        } else {\n            if (plugin.isDungeonItem(itemStack)) {\n                MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_INFO_DUNGEON.getMessage());\n                MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_DUNGEON_ITEM_HELP.getMessage());\n            } else {\n                MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_INFO_GLOBAL.getMessage());\n                MessageUtil.sendMessage(sender, DMessage.CMD_DUNGEON_ITEM_GLOBAL_ITEM_HELP.getMessage());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/EditCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.player.EditPlayerEditEvent;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DEditPlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.config.CommonMessage;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class EditCommand extends DCommand {\n\n    public EditCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"edit\");\n        setMinArgs(1);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_EDIT_HELP.getMessage());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n\n        ResourceWorld resource = plugin.getMapRegistry().getFirstIf(d -> d.getName().equalsIgnoreCase(args[1]));\n        if (resource == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[1]));\n            return;\n        }\n\n        if (!resource.isInvitedPlayer(player) && !DPermission.hasPermission(player, DPermission.EDIT)) {\n            MessageUtil.sendMessage(player, CommonMessage.CMD_NO_PERMISSION.getMessage());\n            return;\n        }\n\n        boolean newlyLoaded = resource.getEditWorld() == null;\n        EditWorld editWorld = resource.getOrInstantiateEditWorld(false);\n        if (editWorld == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_TOO_MANY_INSTANCES.getMessage());\n            return;\n        }\n\n        PlayerGroup dGroup = plugin.getPlayerGroup(player);\n        GlobalPlayer dPlayer = dPlayers.get(player);\n\n        if (dPlayer instanceof InstancePlayer) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n            return;\n        }\n\n        if (dGroup != null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_GROUP.getMessage());\n            return;\n        }\n\n        Bukkit.getPluginManager().callEvent(new EditPlayerEditEvent(new DEditPlayer(plugin, player, editWorld), newlyLoaded));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/EnterCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent.Cause;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class EnterCommand extends DCommand {\n\n    public EnterCommand(DungeonsXL plugin) {\n        super(plugin);\n        setMinArgs(1);\n        setMaxArgs(2);\n        setCommand(\"enter\");\n        setHelp(DMessage.CMD_ENTER_HELP.getMessage());\n        setPermission(DPermission.ENTER.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player captain = (Player) sender;\n        String targetName = args.length == 3 ? args[2] : args[1];\n\n        PlayerGroup joining = args.length == 3 ? plugin.getGroupCache().get(args[1]) : plugin.getPlayerGroup(captain);\n        PlayerGroup target = plugin.getGroupCache().get(targetName);\n\n        if (target == null) {\n            Player targetPlayer = Bukkit.getPlayer(targetName);\n            if (targetPlayer != null) {\n                target = plugin.getPlayerGroup(targetPlayer);\n            }\n        }\n\n        if (target == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_GROUP.getMessage(targetName));\n            return;\n        }\n\n        Game game = target.getGame();\n        if (game == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NOT_IN_GAME.getMessage(targetName));\n            return;\n        }\n\n        if (joining != null && joining.getGame() != null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_LEAVE_GAME.getMessage());\n            return;\n        }\n        if (joining == null) {\n            joining = DGroup.create(plugin, Cause.COMMAND, captain, null, null, game.getDungeon());\n        }\n        if (joining == null) {\n            return;\n        }\n\n        if (joining.getLeader() != captain && !DPermission.hasPermission(sender, DPermission.BYPASS)) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NOT_LEADER.getMessage());\n            return;\n        }\n\n        game.addGroup(joining);\n        joining.sendMessage(DMessage.CMD_ENTER_SUCCESS.getMessage(joining.getName(), target.getName()));\n\n        for (Player player : joining.getMembers().getOnlinePlayers()) {\n            new DGamePlayer(plugin, player, game.getWorld());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/EscapeCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.player.EditPlayerLeaveEvent;\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Milan Albrecht, Daniel Saukel\n */\npublic class EscapeCommand extends DCommand {\n\n    public EscapeCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"escape\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_ESCAPE_HELP.getMessage());\n        setPermission(DPermission.ESCAPE.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        EditPlayer editPlayer = dPlayers.getEditPlayer(player);\n\n        if (dPlayers.getGamePlayer(player) != null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n\n        } else if (editPlayer != null) {\n            EditPlayerLeaveEvent event = new EditPlayerLeaveEvent(editPlayer, true, true);\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                return;\n            }\n\n            editPlayer.escape();\n\n            EditWorld editWorld = editPlayer.getEditWorld();\n            if (editWorld == null) {\n                return;\n            }\n\n            if (editWorld.getWorld().getPlayers().isEmpty() && event.getUnloadIfEmpty()) {\n                editWorld.delete(false);\n            }\n\n        } else {\n            PlayerGroup dGroup = plugin.getPlayerGroup(player);\n            if (dGroup != null) {\n                dGroup.removeMember(player);\n                MessageUtil.sendMessage(player, DMessage.CMD_LEAVE_SUCCESS.getMessage());\n                return;\n            }\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_IN_DUNGEON.getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/GameCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class GameCommand extends DCommand {\n\n    public GameCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"game\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_GAME_HELP.getMessage());\n        setPermission(DPermission.GAME.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        PlayerGroup dGroup = plugin.getPlayerGroup(player);\n        if (dGroup == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        GameWorld gameWorld = dGroup.getGameWorld();\n        if (gameWorld == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_GAME.getMessage());\n            return;\n        }\n\n        DGame game = (DGame) gameWorld.getGame();\n        if (game == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_GAME.getMessage());\n            return;\n        }\n\n        MessageUtil.sendCenteredMessage(sender, \"&4&l[ &6Game &4&l]\");\n        String groups = \"\";\n        for (PlayerGroup group : game.getGroups()) {\n            groups += (group == game.getGroups().get(0) ? \"\" : \"&b, &e\") + group.getName();\n        }\n        MessageUtil.sendMessage(sender, \"&bGroups: &e\" + groups);\n        MessageUtil.sendMessage(sender, \"&bDungeon: &e\" + (dGroup.getDungeon().getName() == null ? \"N/A\" : dGroup.getDungeon().getName()));\n        MessageUtil.sendMessage(sender, \"&bMap: &e\" + (dGroup.getGameWorld() == null ? \"N/A\" : dGroup.getGameWorld().getName()));\n        MessageUtil.sendMessage(sender, \"&bWaves finished: &e\" + game.getWaveCount());\n        MessageUtil.sendMessage(sender, \"&bKills: &e\" + game.getGameKills() + \" / Game; \" + game.getWaveKills() + \" / Wave\");\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/GroupCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent.Cause;\nimport de.erethon.dungeonsxl.api.event.group.GroupDisbandEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerKickEvent;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class GroupCommand extends DCommand {\n\n    public GroupCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"group\");\n        setMinArgs(0);\n        setMaxArgs(2);\n        setHelp(DMessage.CMD_GROUP_HELP_MAIN.getMessage());\n        setPermission(DPermission.GROUP.getNode());\n        setPlayerCommand(true);\n    }\n\n    private CommandSender sender;\n    private Player player;\n    private String[] args;\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        this.sender = sender;\n        this.player = (Player) sender;\n        this.args = args;\n\n        DGroup dGroup = (DGroup) plugin.getPlayerGroup(player);\n\n        if (args.length == 2) {\n\n            if (args[1].equalsIgnoreCase(\"disband\")) {\n                disbandGroup(dGroup, null);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"show\")) {\n                showGroup(dGroup);\n                return;\n            }\n\n        } else if (args.length >= 3) {\n\n            if (args[1].equalsIgnoreCase(\"kick\")) {\n                kickPlayer(dGroup);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"invite\")) {\n                invitePlayer(dGroup);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"uninvite\")) {\n                uninvitePlayer(dGroup);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"help\")) {\n                showHelp(args[2]);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"create\")) {\n                createGroup();\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"disband\") && DPermission.hasPermission(sender, DPermission.GROUP_ADMIN)) {\n                disbandGroup((DGroup) plugin.getGroupCache().get(args[2]), args[2]);\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"join\")) {\n                joinGroup((DGroup) plugin.getGroupCache().get(args[2]));\n                return;\n\n            } else if (args[1].equalsIgnoreCase(\"show\") && DPermission.hasPermission(sender, DPermission.GROUP_ADMIN)) {\n                DGroup group = (DGroup) plugin.getGroupCache().get(args[2]);\n                Player player = Bukkit.getPlayer(args[2]);\n                if (group == null && player != null) {\n                    group = (DGroup) plugin.getPlayerGroup(player);\n                }\n                showGroup(group);\n                return;\n            }\n        }\n\n        showHelp(\"1\");\n    }\n\n    public void createGroup() {\n        if (plugin.getPlayerGroup(player) != null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_LEAVE_GROUP.getMessage());\n            return;\n        }\n\n        if (plugin.getGroupCache().get(args[2]) != null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NAME_IN_USE.getMessage(args[2]));\n            return;\n        }\n\n        DGroup dGroup = DGroup.create(plugin, Cause.COMMAND, player, args[2], null, null);\n        if (dGroup != null) {\n            MessageUtil.sendMessage(sender, DMessage.GROUP_CREATED.getMessage(sender.getName(), args[2]));\n        }\n    }\n\n    public void disbandGroup(DGroup dGroup, String name) {\n        if (dGroup == null) { // only gets here\n            MessageUtil.sendMessage(sender,\n                    name == null ? DMessage.ERROR_SELF_NOT_IN_GROUP.getMessage()\n                            : DMessage.ERROR_NO_SUCH_GROUP.getMessage(name));\n            return;\n        }\n\n        if (dGroup.isPlaying()) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n            return;\n        }\n\n        GroupDisbandEvent event = new GroupDisbandEvent(dGroup, plugin.getPlayerCache().get(player), GroupDisbandEvent.Cause.COMMAND);\n        Bukkit.getPluginManager().callEvent(event);\n\n        if (!event.isCancelled()) {\n            dGroup.delete();\n            MessageUtil.sendMessage(sender, DMessage.GROUP_DISBANDED.getMessage(sender.getName(), dGroup.getName()));\n            dGroup = null;\n        }\n    }\n\n    public void invitePlayer(DGroup dGroup) {\n        if (dGroup == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        Player toInvite = Bukkit.getPlayer(args[2]);\n\n        if (toInvite != null) {\n            dGroup.addInvitedPlayer(toInvite, false);\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[2]));\n        }\n    }\n\n    public void uninvitePlayer(DGroup dGroup) {\n        if (dGroup == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        dGroup.clearOfflineInvitedPlayers();\n\n        Player toUninvite = Bukkit.getPlayer(args[2]);\n\n        if (toUninvite != null) {\n            if (dGroup.getInvitedPlayers().contains(toUninvite)) {\n                dGroup.removeInvitedPlayer(toUninvite, false);\n\n            } else {\n                MessageUtil.sendMessage(sender, DMessage.ERROR_NOT_IN_GROUP.getMessage(args[2]));\n            }\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[2]));\n        }\n    }\n\n    public void joinGroup(DGroup dGroup) {\n        if (dGroup == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_GROUP.getMessage(args[2]));\n            return;\n        }\n\n        if (plugin.getPlayerGroup(player) != null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_LEAVE_GROUP.getMessage());\n            return;\n        }\n\n        if (dGroup.isPlaying()) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_GROUP_IS_PLAYING.getMessage());\n            return;\n        }\n\n        if (!dGroup.getInvitedPlayers().contains(player) && !DPermission.hasPermission(player, DPermission.BYPASS)) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NOT_INVITED.getMessage(args[2]));\n            return;\n        }\n\n        dGroup.addMember(player);\n        dGroup.removeInvitedPlayer(player, true);\n    }\n\n    public void kickPlayer(DGroup dGroup) {\n        if (dGroup == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_JOIN_GROUP.getMessage());\n        }\n\n        Player toKick = Bukkit.getPlayer(args[2]);\n        if (toKick != null) {\n            GroupPlayerKickEvent event = new GroupPlayerKickEvent(dGroup, dPlayers.get(toKick.getPlayer()), GroupPlayerKickEvent.Cause.COMMAND);\n            Bukkit.getPluginManager().callEvent(event);\n\n            if (!event.isCancelled()) {\n                if (dGroup.getMembers().contains(toKick)) {\n                    dGroup.removeMember(toKick);\n                    MessageUtil.sendMessage(sender, DMessage.GROUP_KICKED_PLAYER.getMessage(sender.getName(), args[2], dGroup.getName()));\n\n                } else {\n                    MessageUtil.sendMessage(sender, DMessage.ERROR_NOT_IN_GROUP.getMessage(args[2], dGroup.getName()));\n                }\n            }\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[2]));\n        }\n    }\n\n    public void showGroup(DGroup dGroup) {\n        if (dGroup == null) {\n            if (args.length == 3) {\n                MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_GROUP.getMessage(args[2]));\n\n            } else if (args.length == 2) {\n                MessageUtil.sendMessage(sender, DMessage.ERROR_JOIN_GROUP.getMessage());\n            }\n\n            return;\n        }\n\n        MessageUtil.sendCenteredMessage(sender, \"&4&l[ &6\" + dGroup.getName() + \" &4&l]\");\n        MessageUtil.sendMessage(sender, \"&bCaptain: &e\" + dGroup.getLeader().getName());\n        String players = \"\";\n        for (String player : dGroup.getMembers().getNames()) {\n            players += (players.isEmpty() ? \"\" : \"&b, &e\") + player;\n        }\n        MessageUtil.sendMessage(sender, \"&bPlayers: &e\" + players);\n        MessageUtil.sendMessage(sender, \"&bDungeon: &e\" + (dGroup.getDungeonName() == null ? \"N/A\" : dGroup.getDungeonName()));\n        MessageUtil.sendMessage(sender, \"&bMap: &e\" + (dGroup.getMapName() == null ? \"N/A\" : dGroup.getMapName()));\n        MessageUtil.sendMessage(sender, \"&bScore: &e\" + (dGroup.getScore() == 0 ? \"N/A\" : dGroup.getScore()));\n        MessageUtil.sendMessage(sender, \"&bLives: &e\" + (dGroup.getLives() == -1 ? \"N/A\" : dGroup.getLives()));\n    }\n\n    public void showHelp(String page) {\n        MessageUtil.sendPluginTag(sender, plugin);\n        switch (page) {\n            default:\n                MessageUtil.sendCenteredMessage(sender, \"&4&l[ &61-5 &4/ &67 &4| &61 &4&l]\");\n                MessageUtil.sendMessage(sender, \"&bcreate\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_CREATE.getMessage());\n                MessageUtil.sendMessage(sender, \"&bdisband\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_DISBAND.getMessage());\n                MessageUtil.sendMessage(sender, \"&binvite\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_INVITE.getMessage());\n                MessageUtil.sendMessage(sender, \"&buninvite\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_UNINVITE.getMessage());\n                MessageUtil.sendMessage(sender, \"&bjoin\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_JOIN.getMessage());\n                break;\n            case \"2\":\n                MessageUtil.sendCenteredMessage(sender, \"&4&l[ &66-10 &4/ &67 &4| &62 &4&l]\");\n                MessageUtil.sendMessage(sender, \"&bkick\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_KICK.getMessage());\n                MessageUtil.sendMessage(sender, \"&bshow\" + \"&7 - \" + DMessage.CMD_GROUP_HELP_SHOW.getMessage());\n                break;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/HelpCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.command.DRECommand;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.ArrayList;\nimport java.util.Set;\nimport org.bukkit.command.CommandSender;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class HelpCommand extends DCommand {\n\n    public HelpCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"help\");\n        setMinArgs(0);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_HELP_HELP.getMessage());\n        setPermission(DPermission.HELP.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Set<DRECommand> dCommandList = plugin.getCommandRegistry().getCommands();\n        ArrayList<DRECommand> toSend = new ArrayList<>();\n\n        int page = 1;\n        if (args.length == 2) {\n            page = NumberUtil.parseInt(args[1], 1);\n        }\n        int send = 0;\n        int max = 0;\n        int min = 0;\n        for (DRECommand dCommand : dCommandList) {\n            send++;\n            if (send >= page * 5 - 4 && send <= page * 5) {\n                min = page * 5 - 4;\n                max = page * 5;\n                toSend.add(dCommand);\n            }\n        }\n\n        MessageUtil.sendPluginTag(sender, plugin);\n        MessageUtil.sendCenteredMessage(sender, \"&4&l[ &6\" + min + \"-\" + max + \" &4/&6 \" + send + \" &4|&6 \" + page + \" &4&l]\");\n\n        for (DRECommand dCommand : toSend) {\n            MessageUtil.sendMessage(sender, \"&b\" + dCommand.getCommand() + \"&7 - \" + dCommand.getHelp());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ImportCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.FileUtil;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.dungeonsxl.world.WorldConfig;\nimport java.io.File;\nimport org.bukkit.Bukkit;\nimport org.bukkit.World;\nimport org.bukkit.World.Environment;\nimport org.bukkit.command.CommandSender;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ImportCommand extends DCommand {\n\n    public ImportCommand(DungeonsXL plugin) {\n        super(plugin);\n        setMinArgs(1);\n        setMaxArgs(1);\n        setCommand(\"import\");\n        setHelp(DMessage.CMD_IMPORT_HELP.getMessage());\n        setPermission(DPermission.IMPORT.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        File target = new File(DungeonsXL.MAPS, args[1]);\n        File source = new File(Bukkit.getWorldContainer(), args[1]);\n\n        if (!source.exists()) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[1]));\n            return;\n        }\n\n        if (target.exists()) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NAME_IN_USE.getMessage(args[1]));\n            return;\n        }\n\n        World world = Bukkit.getWorld(args[1]);\n        if (world != null) {\n            world.save();\n        }\n\n        MessageUtil.log(plugin, \"&6Creating new map.\");\n        MessageUtil.log(plugin, \"&6Importing world...\");\n\n        FileUtil.copyDir(source, target, \"playerdata\", \"stats\");\n\n        DResourceWorld resource = new DResourceWorld(plugin, args[1]);\n        plugin.getDungeonRegistry().add(args[1], new DDungeon(plugin, resource));\n        if (world != null && world.getEnvironment() != Environment.NORMAL) {\n            WorldConfig config = resource.getConfig(true);\n            config.setWorldEnvironment(world.getEnvironment());\n            config.save();\n        }\n        plugin.getMapRegistry().add(resource.getName(), resource);\n        MessageUtil.sendMessage(sender, DMessage.CMD_IMPORT_SUCCESS.getMessage(args[1]));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/InviteCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.command.CommandSender;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class InviteCommand extends DCommand {\n\n    public InviteCommand(DungeonsXL plugin) {\n        super(plugin);\n        setMinArgs(2);\n        setMaxArgs(2);\n        setCommand(\"invite\");\n        setHelp(DMessage.CMD_INVITE_HELP.getMessage());\n        setPermission(DPermission.INVITE.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        ResourceWorld resource = plugin.getMapRegistry().get(args[2]);\n        OfflinePlayer player = Bukkit.getOfflinePlayer(args[1]);\n\n        if (resource != null) {\n            if (player != null) {\n                resource.addInvitedPlayer(player);\n                MessageUtil.sendMessage(sender, DMessage.CMD_INVITE_SUCCESS.getMessage(args[1], args[2]));\n\n            } else {\n                MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[2]));\n            }\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[2]));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/KickCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class KickCommand extends DCommand {\n\n    public KickCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"kick\");\n        setMinArgs(1);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_KICK_HELP.getMessage());\n        setPermission(DPermission.KICK.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = Bukkit.getPlayer(args[1]);\n\n        if (player != null) {\n            plugin.getCommandRegistry().leave.onExecute(new String[]{plugin.getCommandRegistry().leave.getCommand()}, player);\n            MessageUtil.sendMessage(sender, DMessage.CMD_KICK_SUCCESS.getMessage(player.getName()));\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[1]));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/LeaveCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerLeaveEvent;\nimport de.erethon.dungeonsxl.api.event.player.EditPlayerLeaveEvent;\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class LeaveCommand extends DCommand {\n\n    public LeaveCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"leave\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_LEAVE_HELP.getMessage());\n        setPermission(DPermission.LEAVE.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer globalPlayer = dPlayers.get(player);\n        Game game = plugin.getGame(player);\n\n        if (game != null && game.isTutorial()) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_LEAVE_IN_TUTORIAL.getMessage());\n            return;\n        }\n\n        PlayerGroup group = globalPlayer.getGroup();\n\n        if (group == null && !(globalPlayer instanceof EditPlayer)) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        if (globalPlayer instanceof GamePlayer) {\n            GroupPlayerLeaveEvent event = new GroupPlayerLeaveEvent(group, globalPlayer);\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                return;\n            }\n            ((GamePlayer) globalPlayer).leave();\n        } else if (globalPlayer instanceof EditPlayer) {\n            EditPlayerLeaveEvent event = new EditPlayerLeaveEvent((EditPlayer) globalPlayer, false, true);\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                return;\n            }\n            ((EditPlayer) globalPlayer).leave(event.getUnloadIfEmpty());\n        } else {\n            group.removeMember(player);\n        }\n\n        MessageUtil.sendMessage(player, DMessage.CMD_LEAVE_SUCCESS.getMessage());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ListCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DungeonConfig;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport java.io.File;\nimport java.util.ArrayList;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class ListCommand extends DCommand {\n\n    public ListCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"list\");\n        setMinArgs(0);\n        setMaxArgs(3);\n        setHelp(DMessage.CMD_LIST_HELP.getMessage());\n        setPermission(DPermission.LIST.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        ArrayList<String> dungeonList = new ArrayList<>();\n        for (Dungeon dungeon : plugin.getDungeonRegistry()) {\n            dungeonList.add(dungeon.getName());\n        }\n        ArrayList<String> mapList = new ArrayList<>();\n        for (File file : DungeonsXL.MAPS.listFiles()) {\n            if (!file.equals(DResourceWorld.RAW)) {\n                mapList.add(file.getName());\n            }\n        }\n        ArrayList<String> loadedList = new ArrayList<>();\n        for (InstanceWorld editWorld : plugin.getInstanceCache().getAllIf(i -> i instanceof EditWorld)) {\n            loadedList.add(editWorld.getWorld().getWorldFolder().getName() + \" / \" + editWorld.getName());\n        }\n        for (InstanceWorld gameWorld : plugin.getInstanceCache().getAllIf(i -> i instanceof GameWorld)) {\n            loadedList.add(gameWorld.getWorld().getWorldFolder().getName() + \" / \" + gameWorld.getName());\n        }\n        ArrayList<String> toSend = new ArrayList<>();\n\n        ArrayList<String> stringList = mapList;\n        boolean specified = false;\n        byte listType = 0;\n        if (args.length >= 2) {\n            if (args[1].equalsIgnoreCase(\"dungeons\") || args[1].equalsIgnoreCase(\"d\")) {\n                if (args.length >= 3) {\n                    Dungeon dungeon = plugin.getDungeonRegistry().get(args[2]);\n                    if (dungeon != null) {\n                        MessageUtil.sendPluginTag(sender, plugin);\n                        MessageUtil.sendCenteredMessage(sender, \"&4&l[ &6\" + dungeon.getName() + \" &4&l]\");\n                        StringBuilder floorList = new StringBuilder();\n                        for (int i = 0; i < dungeon.getFloors().size(); i++) {\n                            if (i != 0) {\n                                floorList.append(\", \");\n                            }\n                            floorList.append(dungeon.getFloors().get(i).getName());\n                        }\n                        MessageUtil.sendMessage(sender, \"&eFloors: &o\" + floorList.toString());\n                        MessageUtil.sendMessage(sender, \"&estartFloor: &o[\" + dungeon.getStartFloor().getName() + \"]\");\n                        MessageUtil.sendMessage(sender, \"&eendFloor: &o[\" + dungeon.getEndFloor().getName() + \"]\");\n                        MessageUtil.sendMessage(sender, \"&efloorCount: &o[\" + dungeon.getFloorCount() + \"]\");\n                        MessageUtil.sendMessage(sender, \"&eremoveWhenPlayed: &o[\" + dungeon.getRemoveWhenPlayed() + \"]\");\n                        return;\n                    }\n                }\n                specified = true;\n                stringList = dungeonList;\n                listType = 1;\n\n            } else if (args[1].equalsIgnoreCase(\"maps\") || args[1].equalsIgnoreCase(\"m\")) {\n                specified = true;\n\n            } else if (args[1].equalsIgnoreCase(\"loaded\") || args[1].equalsIgnoreCase(\"l\")) {\n                specified = true;\n                stringList = loadedList;\n                listType = 2;\n            }\n        }\n\n        int page = 1;\n        if (args.length == 3) {\n            page = NumberUtil.parseInt(args[2], 1);\n\n        } else if (args.length == 2 & !specified) {\n            page = NumberUtil.parseInt(args[1], 1);\n        }\n\n        int send = 0;\n        int max = 0;\n        int min = 0;\n        for (String string : stringList) {\n            send++;\n            if (send >= page * 5 - 4 && send <= page * 5) {\n                min = page * 5 - 4;\n                max = page * 5;\n                toSend.add(string);\n            }\n        }\n\n        MessageUtil.sendPluginTag(sender, plugin);\n        MessageUtil.sendCenteredMessage(sender, \"&4&l[ &6\" + min + \"-\" + max + \" &4/&6 \" + send + \" &4|&6 \" + page + \" &4&l]\");\n\n        switch (listType) {\n            case 0:\n                MessageUtil.sendMessage(sender, \"&4Map&7 | &eInvited\");\n                for (String map : toSend) {\n                    boolean invited = false;\n                    if (sender instanceof Player) {\n                        ResourceWorld resource = plugin.getMapRegistry().get(map);\n                        if (resource != null) {\n                            invited = resource.isInvitedPlayer((Player) sender);\n                        }\n                    }\n\n                    MessageUtil.sendMessage(sender, \"&b\" + map + \"&7 | &e\" + invited);\n                }\n                break;\n            case 1:\n                MessageUtil.sendMessage(sender, \"&4Dungeon&7 | &eMap count\");\n                for (String dungeon : toSend) {\n                    DungeonConfig dungeonConfig = new DungeonConfig(plugin, new File(DungeonsXL.DUNGEONS, dungeon + \".yml\"));\n                    int count = dungeonConfig.getFloors().size() + 2;\n                    MessageUtil.sendMessage(sender, \"&b\" + dungeon + \"&7 | &e\" + count);\n                }\n                break;\n            case 2:\n                MessageUtil.sendMessage(sender, \"&4Loaded map\");\n                for (String map : toSend) {\n                    MessageUtil.sendMessage(sender, \"&b\" + map);\n                }\n                break;\n            default:\n                break;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/LivesCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.config.CommonMessage;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class LivesCommand extends DCommand {\n\n    public LivesCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"lives\");\n        setMinArgs(0);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_LIVES_HELP.getMessage());\n        setPermission(DPermission.LIVES.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = null;\n\n        if (args.length == 2) {\n            if (Bukkit.getPlayer(args[1]) != null) {\n                player = Bukkit.getPlayer(args[1]);\n            }\n\n        } else if (sender instanceof Player) {\n            player = (Player) sender;\n\n        } else {\n            MessageUtil.sendMessage(sender, CommonMessage.CMD_NO_CONSOLE_COMMAND.getMessage(getCommand()));\n            return;\n        }\n\n        GlobalPlayer globalPlayer = dPlayers.get(player);\n        if (!(globalPlayer instanceof GamePlayer)) {\n            MessageUtil.sendMessage(sender, args.length == 1 ? DMessage.ERROR_NO_GAME.getMessage() : DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[1]));\n            return;\n        }\n\n        GamePlayer gamePlayer = (GamePlayer) globalPlayer;\n        PlayerGroup group = gamePlayer != null ? gamePlayer.getGroup() : plugin.getGroupCache().get(args[1]);\n        if (gamePlayer != null) {\n            MessageUtil.sendMessage(sender, DMessage.CMD_LIVES_PLAYER.getMessage(gamePlayer.getName(),\n                    gamePlayer.getLives() == -1 ? DMessage.PLAYER_UNLIMITED_LIVES.getMessage() : String.valueOf(gamePlayer.getLives())));\n\n        } else if (group != null) {\n            MessageUtil.sendMessage(sender, DMessage.CMD_LIVES_GROUP.getMessage(group.getName(),\n                    String.valueOf(group.getLives() == -1 ? \"UNLIMITED\" : group.getLives())));\n\n        } else {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_PLAYER.getMessage(args[1]));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/MainCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport static de.erethon.xlib.chat.FatLetter.*;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.plugin.PluginManager;\n\n/**\n * @author Daniel Saukel\n */\npublic class MainCommand extends DCommand {\n\n    public MainCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"main\");\n        setHelp(DMessage.CMD_MAIN_HELP.getMessage());\n        setPermission(DPermission.MAIN.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        PluginManager plugins = Bukkit.getServer().getPluginManager();\n\n        int maps = DungeonsXL.MAPS.listFiles().length - 1;\n        int dungeons = DungeonsXL.DUNGEONS.listFiles().length;\n        int loaded = plugin.getInstanceCache().size();\n        int players = dPlayers.getAllInstancePlayers().size();\n        String internals = Version.get().getRelocationTarget();\n        String vault = \"\";\n        if (plugins.getPlugin(\"Vault\") != null) {\n            vault = plugins.getPlugin(\"Vault\").getDescription().getVersion();\n        }\n        String xlib = plugins.getPlugin(\"XLib-Runtime\").getDescription().getVersion();\n\n        MessageUtil.sendCenteredMessage(sender, \"&4\" + D[0] + \"&f\" + X[0] + L[0]);\n        MessageUtil.sendCenteredMessage(sender, \"&4\" + D[1] + \"&f\" + X[1] + L[1]);\n        MessageUtil.sendCenteredMessage(sender, \"&4\" + D[2] + \"&f\" + X[2] + L[2]);\n        MessageUtil.sendCenteredMessage(sender, \"&4\" + D[3] + \"&f\" + X[3] + L[3]);\n        MessageUtil.sendCenteredMessage(sender, \"&4\" + D[4] + \"&f\" + X[4] + L[4]);\n        MessageUtil.sendCenteredMessage(sender, \"&b&l###### \" + DMessage.CMD_MAIN_WELCOME.getMessage() + \"&7 v\" + plugin.getDescription().getVersion() + \" &b&l######\");\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_MAIN_LOADED.getMessage(String.valueOf(maps), String.valueOf(dungeons), String.valueOf(loaded), String.valueOf(players)));\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_MAIN_COMPATIBILITY.getMessage(internals, vault, xlib));\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_MAIN_HELP_INFO.getMessage());\n        MessageUtil.sendCenteredMessage(sender, \"&7\\u00a92012-'13 Frank Baumann; '15-'26 Daniel Saukel & contributors.\");\n        MessageUtil.sendCenteredMessage(sender, \"&7Licensed under GPLv3.\");\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/MsgCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.dungeonsxl.world.WorldConfig;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.bukkit.ChatColor;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class MsgCommand extends DCommand {\n\n    public MsgCommand(DungeonsXL plugin) {\n        super(plugin);\n        setMinArgs(-1);\n        setMaxArgs(-1);\n        setCommand(\"message\");\n        setAliases(\"msg\");\n        setHelp(DMessage.CMD_MSG_HELP.getMessage());\n        setPermission(DPermission.MESSAGE.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        EditWorld editWorld = plugin.getEditWorld(player.getWorld());\n\n        if (editWorld == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_IN_DUNGEON.getMessage());\n            return;\n        }\n\n        if (args.length <= 1) {\n            displayHelp(player);\n            return;\n        }\n\n        try {\n            int id = Integer.parseInt(args[1]);\n\n            WorldConfig config = ((DResourceWorld) editWorld.getResource()).getConfig(true);\n            Map<Integer, String> msgs = config.getState(GameRule.MESSAGES);\n            if (msgs == null) {\n                config.setState(GameRule.MESSAGES, new HashMap<>());\n                msgs = config.getState(GameRule.MESSAGES);\n            }\n\n            if (args.length == 2) {\n                String msg = msgs.get(id);\n\n                if (msg != null) {\n                    MessageUtil.sendMessage(player, ChatColor.WHITE + msg);\n\n                } else {\n                    MessageUtil.sendMessage(player, DMessage.ERROR_MSG_ID_NOT_EXIST.getMessage(String.valueOf(id)));\n                }\n\n            } else {\n                String msg = \"\";\n                int i = 0;\n                for (String arg : args) {\n                    i++;\n                    if (i > 2) {\n                        msg = msg + \" \" + arg;\n                    }\n                }\n\n                String[] splitMsg = msg.split(\"\\\"\");\n\n                if (splitMsg.length > 1) {\n                    msg = splitMsg[1];\n                    String old = msgs.get(id);\n                    if (old == null) {\n                        MessageUtil.sendMessage(player, DMessage.CMD_MSG_ADDED.getMessage(String.valueOf(id)));\n\n                    } else {\n                        MessageUtil.sendMessage(player, DMessage.CMD_MSG_UPDATED.getMessage(String.valueOf(id)));\n                    }\n\n                    msgs.put(id, msg);\n                    config.save();\n\n                    for (Dungeon dungeon : plugin.getDungeonRegistry()) {\n                        if (!dungeon.getStartFloor().equals(editWorld.getResource())) {\n                            continue;\n                        }\n                        // Only MFD overrideValues can override floor configs\n                        if (dungeon.isMultiFloor()) {\n                            Map<Integer, String> overrideValuesMSG = dungeon.getOverrideValues().getState(GameRule.MESSAGES);\n                            if (overrideValuesMSG != null && overrideValuesMSG.containsKey(id)) {\n                                continue;\n                            }\n                        }\n                        dungeon.getRules().getState(GameRule.MESSAGES).put(id, msg);\n                    }\n\n                } else {\n                    MessageUtil.sendMessage(player, DMessage.ERROR_MSG_FORMAT.getMessage());\n                }\n            }\n\n        } catch (NumberFormatException e) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_MSG_NO_INT.getMessage());\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/PlayCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DInstancePlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class PlayCommand extends DCommand {\n\n    public PlayCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"play\");\n        setMinArgs(1);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_PLAY_HELP.getMessage());\n        setPermission(DPermission.PLAY.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(false);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer dPlayer = dPlayers.get(player);\n        if (dPlayer instanceof DInstancePlayer) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n            return;\n        }\n\n        Dungeon dungeon = plugin.getDungeonRegistry().get(args[1]);\n        if (dungeon == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_SUCH_DUNGEON.getMessage(args[1]));\n            return;\n        }\n\n        DGroup group = (DGroup) dPlayer.getGroup();\n        if (group != null && group.isPlaying()) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_GROUP.getMessage());\n            return;\n        } else if (group == null) {\n            group = DGroup.create(plugin, GroupCreateEvent.Cause.COMMAND, player, null, null, dungeon);\n            if (group == null) {\n                return;\n            }\n        }\n        if (!group.getLeader().equals(player) && !DPermission.hasPermission(player, DPermission.BYPASS)) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_LEADER.getMessage());\n            return;\n        }\n        group.setDungeon(dungeon);\n\n        if (!dPlayer.checkRequirements(dungeon)) {\n            return;\n        }\n\n        Game game = new DGame(plugin, dungeon, group);\n        GameWorld gameWorld = game.ensureWorldIsLoaded(false);\n        if (gameWorld == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_TOO_MANY_INSTANCES.getMessage());\n            return;\n        }\n        for (Player groupPlayer : group.getMembers().getOnlinePlayers()) {\n            new DGamePlayer(plugin, groupPlayer, group.getGameWorld());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/PortalCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.global.DPortal;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class PortalCommand extends DCommand {\n\n    XLib xlib = plugin.getXLib();\n\n    public PortalCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"portal\");\n        setMinArgs(0);\n        setMaxArgs(2);\n        setHelp(DMessage.CMD_PORTAL_HELP.getMessage());\n        setPermission(DPermission.PORTAL.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        DGlobalPlayer dGlobalPlayer = (DGlobalPlayer) dPlayers.get(player);\n\n        if (dGlobalPlayer instanceof DGamePlayer) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n            return;\n        }\n\n        ExItem material = null;\n\n        if (args.length == 2) {\n            material = xlib.getExItem(args[1]);\n        }\n\n        if (material == null) {\n            material = VanillaItem.NETHER_PORTAL;\n        }\n\n        DPortal dPortal = dGlobalPlayer.getPortal();\n\n        if (dPortal == null) {\n            dPortal = new DPortal(plugin, plugin.getGlobalProtectionCache().generateId(DPortal.class, player.getWorld()), player.getWorld(), material, false);\n            plugin.getGlobalProtectionCache().addProtection(dPortal);\n            dGlobalPlayer.setCreatingPortal(dPortal);\n            dGlobalPlayer.setCachedItem(player.getInventory().getItemInHand());\n            player.getInventory().setItemInHand(VanillaItem.WOODEN_SWORD.toItemStack());\n            MessageUtil.sendMessage(player, DMessage.PLAYER_PORTAL_INTRODUCTION.getMessage());\n\n        } else {\n            if (args.length == 3 && VanillaItem.NETHER_PORTAL.getId().equalsIgnoreCase(args[1])) {\n                if (args[2].equalsIgnoreCase(\"-rotate\")) {\n                    dPortal.rotate();\n                }\n                dGlobalPlayer.setCreatingPortal(null);\n                MessageUtil.sendMessage(player, DMessage.PLAYER_PORTAL_CREATED.getMessage());\n                return;\n            }\n\n            dPortal.delete();\n            dGlobalPlayer.setCreatingPortal(null);\n            player.getInventory().setItemInHand(dGlobalPlayer.getCachedItem());\n            dGlobalPlayer.setCachedItem(null);\n            MessageUtil.sendMessage(player, DMessage.PLAYER_PORTAL_ABORT.getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ReloadCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.DataReloadEvent;\nimport de.erethon.dungeonsxl.api.player.GroupAdapter;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.DefaultFontInfo;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport java.util.Collection;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.TextComponent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.plugin.PluginManager;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ReloadCommand extends DCommand {\n\n    public ReloadCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"reload\");\n        setMinArgs(0);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_RELOAD_HELP.getMessage());\n        setPermission(DPermission.RELOAD.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        if (plugin.isLoadingWorld()) {\n            MessageUtil.sendMessage(sender, DMessage.CMD_RELOAD_FAIL.getMessage());\n            return;\n        }\n\n        Collection<InstancePlayer> dPlayers = this.dPlayers.getAllInstancePlayers();\n        if (!dPlayers.isEmpty() && args.length == 1 && sender instanceof Player) {\n            MessageUtil.sendMessage(sender, DMessage.CMD_RELOAD_PLAYERS.getMessage());\n            ClickEvent onClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/dungeonsxl reload -force\");\n            String message = DefaultFontInfo.getCenterSpaces(DMessage.BUTTON_OKAY.getMessage()) + DMessage.BUTTON_OKAY.getMessage();\n            TextComponent text = new TextComponent(message);\n            text.setClickEvent(onClick);\n            ((Player) sender).spigot().sendMessage(text);\n            return;\n        }\n\n        PluginManager plugins = Bukkit.getPluginManager();\n\n        DataReloadEvent event = new DataReloadEvent();\n        plugins.callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        dPlayers.forEach(InstancePlayer::leave);\n\n        int maps = DungeonsXL.MAPS.listFiles().length - 1;\n        int dungeons = DungeonsXL.DUNGEONS.listFiles().length;\n        int loaded = plugin.getInstanceCache().size();\n        int players = this.dPlayers.getAllGamePlayers().size();\n        String internals = Version.get().getRelocationTarget();\n        String vault = \"\";\n        if (plugins.getPlugin(\"Vault\") != null) {\n            vault = plugins.getPlugin(\"Vault\").getDescription().getVersion();\n        }\n        String xlib = plugins.getPlugin(\"XLib-Runtime\").getDescription().getVersion();\n\n        plugin.saveData();\n        plugin.initFolders();\n        plugin.reload();\n        plugin.checkState();\n        plugin.getGroupAdapters().forEach(GroupAdapter::clear);\n\n        MessageUtil.sendPluginTag(sender, plugin);\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_RELOAD_SUCCESS.getMessage());\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_MAIN_LOADED.getMessage(String.valueOf(maps), String.valueOf(dungeons), String.valueOf(loaded), String.valueOf(players)));\n        MessageUtil.sendCenteredMessage(sender, DMessage.CMD_MAIN_COMPATIBILITY.getMessage(internals, vault, xlib));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/RenameCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.dungeon.DungeonConfig;\nimport de.erethon.dungeonsxl.global.GlobalProtection;\nimport de.erethon.dungeonsxl.global.JoinSign;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.List;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.configuration.file.FileConfiguration;\n\n/**\n * @author Daniel Saukel\n */\npublic class RenameCommand extends DCommand {\n\n    public RenameCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"rename\");\n        setMinArgs(2);\n        setMaxArgs(2);\n        setHelp(DMessage.CMD_RENAME_HELP.getMessage());\n        setPermission(DPermission.RENAME.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        DResourceWorld resource = (DResourceWorld) plugin.getMapRegistry().get(args[1]);\n        if (resource == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[1]));\n            return;\n        }\n\n        Dungeon sfd = resource.getSingleFloorDungeon();\n        resource.setName(args[2]);\n        resource.getFolder().renameTo(new File(DungeonsXL.MAPS, args[2]));\n        resource.getSignData().updateFile(resource);\n\n        if (resource.getEditWorld() != null) {\n            resource.getEditWorld().delete(true);\n        }\n\n        for (Dungeon dungeon : plugin.getDungeonRegistry()) {\n            if (!dungeon.isMultiFloor()) {\n                continue;\n            }\n            DungeonConfig dConfig = ((DDungeon) dungeon).getConfig();\n            FileConfiguration config = dConfig.getConfig();\n            File file = dConfig.getFile();\n\n            if (dConfig.getStartFloor() == resource) {\n                config.set(\"startFloor\", args[2]);\n            }\n\n            if (dConfig.getEndFloor() == resource) {\n                config.set(\"endFloor\", args[2]);\n            }\n\n            List<String> list = config.getStringList(\"floors\");\n            int i = 0;\n            for (ResourceWorld floor : dConfig.getFloors()) {\n                if (floor == resource) {\n                    list.set(i, args[2]);\n                }\n                i++;\n            }\n            config.set(\"floors\", list);\n\n            try {\n                config.save(file);\n            } catch (IOException ex) {\n            }\n        }\n        sfd.setName(args[2]);\n        plugin.getDungeonRegistry().removeKey(args[1]);\n        plugin.getDungeonRegistry().add(args[2], sfd);\n        plugin.getMapRegistry().removeKey(args[1]);\n        plugin.getMapRegistry().add(args[2], resource);\n\n        boolean changed = false;\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections().toArray(GlobalProtection[]::new)) {\n            if (!(protection instanceof JoinSign)) {\n                continue;\n            }\n            Dungeon dungeon = ((JoinSign) protection).getDungeon();\n            if (dungeon == null) {\n                protection.delete();\n                continue;\n            }\n            if (dungeon.getName().equals(args[1])) {\n                dungeon.setName(args[2]);\n                changed = true;\n            }\n        }\n\n        if (changed) {\n            plugin.getGlobalProtectionCache().saveAll();\n        }\n\n        MessageUtil.sendMessage(sender, DMessage.CMD_RENAME_SUCCESS.getMessage(args[1], args[2]));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/ResourcePackCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class ResourcePackCommand extends DCommand {\n\n    public ResourcePackCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"resourcepack\");\n        setMinArgs(1);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_RESOURCE_PACK_HELP.getMessage());\n        setPermission(DPermission.RESOURCE_PACK.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n\n        if (args[1].equalsIgnoreCase(\"reset\")) {\n            // Placeholder to reset to default\n            player.setResourcePack(\"http://google.com\");\n            return;\n        }\n\n        String url = (String) config.getResourcePacks().get(args[1]);\n        if (url == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_RESOURCE_PACK.getMessage(args[1]));\n            return;\n        }\n\n        player.setResourcePack(url);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/SaveCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.config.MainConfig.BackupMode;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class SaveCommand extends DCommand {\n\n    public SaveCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"save\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_SAVE_HELP.getMessage());\n        setPermission(DPermission.SAVE.getNode());\n        setPlayerCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        EditWorld editWorld = plugin.getEditWorld(player.getWorld());\n        if (editWorld != null) {\n            BackupMode backupMode = config.getBackupMode();\n            if (backupMode == BackupMode.ON_SAVE || backupMode == BackupMode.ON_DISABLE_AND_SAVE) {\n                editWorld.getResource().backup();\n            }\n\n            editWorld.save();\n            MessageUtil.sendMessage(player, DMessage.CMD_SAVE_SUCCESS.getMessage());\n\n        } else {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_IN_DUNGEON.getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/StatusCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.DependencyVersion;\nimport static de.erethon.dungeonsxl.util.DependencyVersion.*;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.md_5.bungee.api.chat.HoverEvent;\nimport net.md_5.bungee.api.chat.TextComponent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class StatusCommand extends DCommand {\n\n    public static final String TRUE = ChatColor.GREEN + \"\\u2714\";\n    public static final String FALSE = ChatColor.DARK_RED + \"\\u2718\";\n\n    public StatusCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"status\");\n        setMinArgs(0);\n        setMaxArgs(0);\n        setHelp(DMessage.CMD_STATUS_HELP.getMessage());\n        setPermission(DPermission.STATUS.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        String minecraftVersion = Version.get().toString();\n        String bukkitVersion = Bukkit.getBukkitVersion();\n        String dungeonsxlVersion = plugin.getDescription().getVersion();\n\n        boolean atLeastMin = Version.isAtLeast(DependencyVersion.META.getMinVersion());\n        boolean atMostMax = Version.isAtMost(Version.values()[Version.values().length - 2]);\n        String versionCorrect = getSymbol(atLeastMin && atMostMax);\n        if (atLeastMin && !atMostMax) {\n            versionCorrect += \"(possibly works through forward compatibility)\";\n        }\n        String dungeonsxlVersionCorrect = getSymbol(!dungeonsxlVersion.contains(\"SNAPSHOT\"));\n\n        MessageUtil.sendCenteredMessage(sender, \"&4&l=> &6STATUS &4&l<=\");\n        MessageUtil.sendMessage(sender, ChatColor.GRAY + \"Version info:\");\n        MessageUtil.sendMessage(sender, \"= Minecraft: \" + minecraftVersion + \" \" + versionCorrect);\n        MessageUtil.sendMessage(sender, \"= Bukkit: \" + bukkitVersion + \" \" + versionCorrect);\n        MessageUtil.sendMessage(sender, \"= DungeonsXL: \" + dungeonsxlVersion + \" \" + dungeonsxlVersionCorrect);\n\n        String permissionPlugin = \"No plugin found\";\n        String economyPlugin = \"No plugin found\";\n        if (VAULT.isEnabled()) {\n            if (xlib.getPermissionProvider() != null) {\n                permissionPlugin = xlib.getPermissionProvider().getName();\n            }\n            if (xlib.getEconomyProvider() != null) {\n                economyPlugin = xlib.getEconomyProvider().getName();\n            }\n        }\n        String permissionPluginCorrect = getSymbol(xlib.getPermissionProvider() != null && xlib.getPermissionProvider().hasGroupSupport());\n        String economyPluginCorrect = getSymbol(!plugin.getMainConfig().isEconomyEnabled() || xlib.getEconomyProvider() != null);\n\n        MessageUtil.sendMessage(sender, ChatColor.GRAY + \"Dependency info:\");\n        for (DependencyVersion dependency : DependencyVersion.values()) {\n            if (sender instanceof Player) {\n                ((Player) sender).spigot().sendMessage(statusMsg(dependency));\n            } else {\n                MessageUtil.sendMessage(sender, msgText(dependency));\n            }\n            if (dependency == VAULT) {\n                MessageUtil.sendMessage(sender, \"  = Permissions: \" + permissionPlugin + \" \" + permissionPluginCorrect);\n                MessageUtil.sendMessage(sender, \"  = Economy: \" + economyPlugin + \" \" + economyPluginCorrect);\n            }\n        }\n    }\n\n    private static String msgText(DependencyVersion dependency) {\n        return \"= \" + dependency.getName() + \": \" + dependency.getEnabledVersion() + \" \" + getSymbol(dependency.check());\n    }\n\n    private static BaseComponent statusMsg(DependencyVersion dependency) {\n        TextComponent text = new TextComponent(msgText(dependency));\n        if (!dependency.check()) {\n            HoverEvent event = new HoverEvent(\n                    HoverEvent.Action.SHOW_TEXT,\n                    new ComponentBuilder(\"The tested version is: \").color(ChatColor.GRAY)\n                            .append(dependency.getSupportedVersion()).color(ChatColor.GREEN).create());\n            text.setHoverEvent(event);\n        }\n        return text;\n    }\n\n    private static String getSymbol(boolean value) {\n        return value ? TRUE : FALSE;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/TestCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DInstancePlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.config.CommonMessage;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class TestCommand extends DCommand {\n\n    public TestCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"test\");\n        setMinArgs(1);\n        setMaxArgs(1);\n        setHelp(DMessage.CMD_TEST_HELP.getMessage());\n        setPlayerCommand(true);\n        setConsoleCommand(false);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        Player player = (Player) sender;\n        GlobalPlayer dPlayer = dPlayers.get(player);\n        if (dPlayer instanceof DInstancePlayer) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_DUNGEON.getMessage());\n            return;\n        }\n\n        Dungeon dungeon = plugin.getDungeonRegistry().get(args[1]);\n        if (dungeon == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_SUCH_DUNGEON.getMessage(args[1]));\n            return;\n        }\n\n        if (!dungeon.getMap().isInvitedPlayer(player) && !DPermission.hasPermission(player, DPermission.TEST)) {\n            MessageUtil.sendMessage(player, CommonMessage.CMD_NO_PERMISSION.getMessage());\n            return;\n        }\n\n        DGroup group = (DGroup) dPlayer.getGroup();\n        if (group != null && group.isPlaying()) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_GROUP.getMessage());\n            return;\n        } else if (group == null) {\n            group = DGroup.create(plugin, GroupCreateEvent.Cause.COMMAND, player, null, null, dungeon);\n            if (group == null) {\n                return;\n            }\n        }\n        if (!group.getLeader().equals(player) && !DPermission.hasPermission(player, DPermission.BYPASS)) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_LEADER.getMessage());\n            return;\n        }\n        group.setDungeon(dungeon);\n\n        if (!dPlayer.checkRequirements(dungeon)) {\n            return;\n        }\n\n        Game game = new DGame(plugin, dungeon, group);\n        game.setRewards(false);\n        GameWorld gameWorld = game.ensureWorldIsLoaded(false);\n        if (gameWorld == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_TOO_MANY_INSTANCES.getMessage());\n            return;\n        }\n        for (Player groupPlayer : group.getMembers().getOnlinePlayers()) {\n            new DGamePlayer(plugin, groupPlayer, group.getGameWorld());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/command/UninviteCommand.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.command;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.command.CommandSender;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class UninviteCommand extends DCommand {\n\n    public UninviteCommand(DungeonsXL plugin) {\n        super(plugin);\n        setCommand(\"uninvite\");\n        setMinArgs(2);\n        setMaxArgs(2);\n        setHelp(DMessage.CMD_UNINVITE_HELP.getMessage());\n        setPermission(DPermission.UNINVITE.getNode());\n        setPlayerCommand(true);\n        setConsoleCommand(true);\n    }\n\n    @Override\n    public void onExecute(String[] args, CommandSender sender) {\n        ResourceWorld resource = plugin.getMapRegistry().get(args[2]);\n        if (resource == null) {\n            MessageUtil.sendMessage(sender, DMessage.ERROR_NO_SUCH_MAP.getMessage(args[2]));\n            return;\n        }\n\n        OfflinePlayer player = Bukkit.getOfflinePlayer(args[1]);\n        if (resource.removeInvitedPlayer(player)) {\n            MessageUtil.sendMessage(sender, DMessage.CMD_UNINVITE_SUCCESS.getMessage(args[1], args[2]));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/config/DMessage.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.config;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.config.Message;\nimport de.erethon.xlib.config.MessageHandler;\n\n/**\n * An enumeration of all messages. The values are fetched from the language file.\n *\n * @author Daniel Saukel\n */\npublic enum DMessage implements Message {\n\n    ANNOUNCER_CLICK(\"announcer.click\"),\n    BUTTON_ACCEPT(\"button.accept\"),\n    BUTTON_DENY(\"button.deny\"),\n    BUTTON_OKAY(\"button.okay\"),\n    CMD_ANNOUNCE_HELP(\"cmd.announce.help\"),\n    CMD_BREAK_BREAK_MODE(\"cmd.break.breakMode\"),\n    CMD_BREAK_HELP(\"cmd.break.help\"),\n    CMD_BREAK_PROTECTED_MODE(\"cmd.break.protectedMode\"),\n    CMD_CHAT_HELP(\"cmd.chat.help\"),\n    CMD_CHAT_DUNGEON_CHAT(\"cmd.chat.dungeonChat\"),\n    CMD_CHAT_NORMAL_CHAT(\"cmd.chat.normalChat\"),\n    CMD_CHATSPY_HELP(\"cmd.chatspy.help\"),\n    CMD_CHATSPY_STOPPED(\"cmd.chatspy.stopped\"),\n    CMD_CHATSPY_STARTED(\"cmd.chatspy.started\"),\n    CMD_CREATE_HELP(\"cmd.create.help\"),\n    CMD_DELETE_BACKUPS(\"cmd.delete.backups\"),\n    CMD_DELETE_HELP(\"cmd.delete.help\"),\n    CMD_DELETE_SUCCESS(\"cmd.delete.success\"),\n    CMD_DUNGEON_ITEM_HELP(\"cmd.dungeonItem.help\"),\n    CMD_DUNGEON_ITEM_DUNGEON_ITEM_HELP(\"cmd.dungeonItem.dungeonItemHelp\"),\n    CMD_DUNGEON_ITEM_GLOBAL_ITEM_HELP(\"cmd.dungeonItem.globalItemHelp\"),\n    CMD_DUNGEON_ITEM_INFO_DUNGEON(\"cmd.dungeonItem.info.dungeon\"),\n    CMD_DUNGEON_ITEM_INFO_GLOBAL(\"cmd.dungeonItem.info.global\"),\n    CMD_DUNGEON_ITEM_SET_DUNGEON(\"cmd.dungeonItem.set.dungeon\"),\n    CMD_DUNGEON_ITEM_SET_GLOBAL(\"cmd.dungeonItem.set.global\"),\n    CMD_EDIT_HELP(\"cmd.edit.help\"),\n    CMD_ENTER_HELP(\"cmd.enter.help\"),\n    CMD_ENTER_SUCCESS(\"cmd.enter.success\"),\n    CMD_ESCAPE_HELP(\"cmd.escape.help\"),\n    CMD_GAME_HELP(\"cmd.game.help\"),\n    CMD_GROUP_HELP_MAIN(\"cmd.group.help.main\"),\n    CMD_GROUP_HELP_CREATE(\"cmd.group.help.create\"),\n    CMD_GROUP_HELP_DISBAND(\"cmd.group.help.disband\"),\n    CMD_GROUP_HELP_INVITE(\"cmd.group.help.invite\"),\n    CMD_GROUP_HELP_JOIN(\"cmd.group.help.join\"),\n    CMD_GROUP_HELP_KICK(\"cmd.group.help.kick\"),\n    CMD_GROUP_HELP_SHOW(\"cmd.group.help.show\"),\n    CMD_GROUP_HELP_UNINVITE(\"cmd.group.help.uninvite\"),\n    CMD_HELP_HELP(\"cmd.help.help\"),\n    CMD_IMPORT_HELP(\"cmd.import.help\"),\n    CMD_IMPORT_SUCCESS(\"cmd.import.success\"),\n    CMD_INVITE_HELP(\"cmd.invite.help\"),\n    CMD_INVITE_SUCCESS(\"cmd.invite.success\"),\n    CMD_JOIN_HELP(\"cmd.join.help\"),\n    CMD_KICK_HELP(\"cmd.kick.help\"),\n    CMD_KICK_SUCCESS(\"cmd.kick.success\"),\n    CMD_LEAVE_HELP(\"cmd.leave.help\"),\n    CMD_LEAVE_SUCCESS(\"cmd.leave.success\"),\n    CMD_LIST_HELP(\"cmd.list.help\"),\n    CMD_LIVES_GROUP(\"cmd.lives.group\"),\n    CMD_LIVES_HELP(\"cmd.lives.help\"),\n    CMD_LIVES_PLAYER(\"cmd.lives.player\"),\n    CMD_MAIN_WELCOME(\"cmd.main.welcome\"),\n    CMD_MAIN_LOADED(\"cmd.main.loaded\"),\n    CMD_MAIN_COMPATIBILITY(\"cmd.main.compatibility\"),\n    CMD_MAIN_HELP(\"cmd.main.help\"),\n    CMD_MAIN_HELP_INFO(\"cmd.main.helpInfo\"),\n    CMD_MSG_ADDED(\"cmd.msg.added\"),\n    CMD_MSG_HELP(\"cmd.msg.help\"),\n    CMD_MSG_UPDATED(\"cmd.msg.updated\"),\n    CMD_PORTAL_HELP(\"cmd.portal.help\"),\n    CMD_PLAY_HELP(\"cmd.play.help\"),\n    CMD_RELOAD_FAIL(\"cmd.reload.fail\"),\n    CMD_RELOAD_HELP(\"cmd.reload.help\"),\n    CMD_RELOAD_SUCCESS(\"cmd.reload.success\"),\n    CMD_RELOAD_PLAYERS(\"cmd.reload.players\"),\n    CMD_RENAME_HELP(\"cmd.rename.help\"),\n    CMD_RENAME_SUCCESS(\"cmd.rename.success\"),\n    CMD_RESOURCE_PACK_HELP(\"cmd.resourcePack.help\"),\n    CMD_SAVE_HELP(\"cmd.save.help\"),\n    CMD_SAVE_SUCCESS(\"cmd.save.success\"),\n    CMD_STATUS_HELP(\"cmd.status.help\"),\n    CMD_TEST_HELP(\"cmd.test.help\"),\n    CMD_UNINVITE_HELP(\"cmd.uninvite.help\"),\n    CMD_UNINVITE_SUCCESS(\"cmd.uninvite.success\"),\n    DAY_OF_WEEK_0(\"dayOfWeek.0\"),\n    DAY_OF_WEEK_1(\"dayOfWeek.1\"),\n    DAY_OF_WEEK_2(\"dayOfWeek.2\"),\n    DAY_OF_WEEK_3(\"dayOfWeek.3\"),\n    DAY_OF_WEEK_4(\"dayOfWeek.4\"),\n    DAY_OF_WEEK_5(\"dayOfWeek.5\"),\n    DAY_OF_WEEK_6(\"dayOfWeek.6\"),\n    ERROR_BED(\"error.bed\"),\n    ERROR_CHEST_IS_OPENED(\"error.chestIsOpened\"),\n    ERROR_CMD(\"error.cmd\"),\n    ERROR_DISPENSER(\"error.dispenser\"),\n    ERROR_DROP(\"error.drop\"),\n    ERROR_ENDERCHEST(\"error.enderchest\"),\n    ERROR_GROUP_IS_PLAYING(\"error.groupIsPlaying\"),\n    ERROR_IN_GROUP(\"error.inGroup\"),\n    ERROR_JOIN_GROUP(\"error.joinGroup\"),\n    ERROR_LEAVE_DUNGEON(\"error.leaveDungeon\"),\n    ERROR_LEAVE_GAME(\"error.leaveGame\"),\n    ERROR_LEAVE_GROUP(\"error.leaveGroup\"),\n    ERROR_MSG_ID_NOT_EXIST(\"error.msgIdDoesNotExist\"),\n    ERROR_MSG_FORMAT(\"error.msgFormat\"),\n    ERROR_MSG_NO_INT(\"error.msgNoInt\"),\n    ERROR_NAME_IN_USE(\"error.nameInUse\"),\n    ERROR_NAME_TOO_LONG(\"error.nameTooLong\"),\n    ERROR_NO_GAME(\"error.noGame\"),\n    ERROR_NO_ITEM_IN_MAIN_HAND(\"error.noItemInMainHand\"),\n    ERROR_NO_LEAVE_IN_TUTORIAL(\"error.noLeaveInTutorial\"),\n    ERROR_NO_PERMISSIONS(\"error.noPermissions\"),\n    ERROR_NO_PROTECTED_BLOCK(\"error.noProtectedBlock\"),\n    ERROR_NO_READY_SIGN(\"error.noReadySign\"),\n    ERROR_NO_REWARDS_TIME(\"error.noRewardsTime\"),\n    ERROR_NO_SUCH_ANNOUNCER(\"error.noSuchAnnouncer\"),\n    ERROR_NO_SUCH_DUNGEON(\"error.noSuchDungeon\"),\n    ERROR_NO_SUCH_GROUP(\"error.noSuchGroup\"),\n    ERROR_NO_SUCH_MAP(\"error.noSuchMap\"),\n    ERROR_NO_SUCH_PLAYER(\"error.noSuchPlayer\"),\n    ERROR_NO_SUCH_RESOURCE_PACK(\"error.noSuchResourcePack\"),\n    ERROR_NO_SUCH_SHOP(\"error.noSuchShop\"),\n    ERROR_NOT_IN_DUNGEON(\"error.notInDungeon\"),\n    ERROR_NOT_IN_GAME(\"error.notInGame\"),\n    ERROR_NOT_IN_GROUP(\"error.notInGroup\"),\n    ERROR_NOT_INVITED(\"error.notInvited\"),\n    ERROR_NOT_LEADER(\"error.notLeader\"),\n    ERROR_NOT_SAVED(\"error.notSaved\"),\n    ERROR_BLOCK_OWN_TEAM(\"error.blockOwnTeam\"),\n    ERROR_READY(\"error.ready\"),\n    ERROR_REQUIREMENTS(\"error.requirements\"),\n    ERROR_SELF_NOT_IN_GROUP(\"error.selfNotInGroup\"),\n    ERROR_SIGN_WRONG_FORMAT(\"error.signWrongFormat\"),\n    ERROR_TOO_MANY_INSTANCES(\"error.tooManyInstances\"),\n    ERROR_TOO_MANY_TUTORIALS(\"error.tooManyTutorials\"),\n    ERROR_TUTORIAL_DOES_NOT_EXIST(\"error.tutorialDoesNotExist\"),\n    GROUP_BED_DESTROYED(\"group.bedDestroyed\"),\n    GROUP_CONGRATS(\"group.congrats\"),\n    GROUP_CONGRATS_SUB(\"group.congratsSub\"),\n    GROUP_CREATED(\"group.created\"),\n    GROUP_DEATH(\"group.death\"),\n    GROUP_DEATH_KICK(\"group.deathKick\"),\n    GROUP_DEFEATED(\"group.defeated\"),\n    GROUP_DISBANDED(\"group.disbanded\"),\n    GROUP_FLAG_CAPTURED(\"group.flagCaptured\"),\n    GROUP_FLAG_LOST(\"group.flagLost\"),\n    GROUP_FLAG_STEALING(\"group.flagStealing\"),\n    GROUP_INVITED_PLAYER(\"group.invitedPlayer\"),\n    GROUP_JOINED_GAME(\"group.joinedGame\"),\n    GROUP_KILLED(\"group.killed\"),\n    GROUP_KILLED_KICK(\"group.killedKick\"),\n    GROUP_LIVES_ADDED(\"group.livesAdded\"),\n    GROUP_LIVES_REMOVED(\"group.livesRemoved\"),\n    GROUP_KICKED_PLAYER(\"group.kickedPlayer\"),\n    GROUP_PLAYER_JOINED(\"group.playerJoined\"),\n    GROUP_REWARD_CHEST(\"group.rewardChest\"),\n    GROUP_UNINVITED_PLAYER(\"group.uninvitedPlayer\"),\n    GROUP_WAVE_FINISHED(\"group.waveFinished\"),\n    PLAYER_BLOCK_INFO(\"player.blockInfo\"),\n    PLAYER_CHECKPOINT_REACHED(\"player.checkpointReached\"),\n    PLAYER_DEATH(\"player.death\"),\n    PLAYER_DEATH_KICK(\"player.deathKick\"),\n    PLAYER_FINISHED_DUNGEON(\"player.finishedDungeon\"),\n    PLAYER_FINISHED_FLOOR(\"player.finished_Floor\"),\n    PLAYER_INVITED(\"player.invited\"),\n    PLAYER_UNINVITED(\"player.uninvited\"),\n    PLAYER_JOIN_GROUP(\"player.joinGroup\"),\n    PLAYER_KICKED(\"player.kicked\"),\n    PLAYER_KILLED(\"player.killed\"),\n    PLAYER_KILLED_KICK(\"player.killedKick\"),\n    PLAYER_LEAVE_GROUP(\"player.leaveGroup\"),\n    PLAYER_LEFT_GROUP(\"player.leftGroup\"),\n    PLAYER_LIVES_ADDED(\"player.livesAdded\"),\n    PLAYER_LIVES_REMOVED(\"player.livesRemoved\"),\n    PLAYER_LOOT_ADDED(\"player.lootAdded\"),\n    PLAYER_NEW_LEADER(\"player.newLeader\"),\n    PLAYER_OFFLINE(\"player.offline\"),\n    PLAYER_OFFLINE_NEVER(\"player.offlineNever\"),\n    PLAYER_PORTAL_ABORT(\"player.portal.abort\"),\n    PLAYER_PORTAL_INTRODUCTION(\"player.portal.introduction\"),\n    PLAYER_PORTAL_CREATED(\"player.portal.created\"),\n    PLAYER_PORTAL_PROGRESS(\"player.portal.progress\"),\n    PLAYER_PORTAL_ROTATE(\"player.portal.rotate\"),\n    PLAYER_PROTECTED_BLOCK_DELETED(\"player.protectedBlockDeleted\"),\n    PLAYER_READY(\"player.ready\"),\n    PLAYER_SIGN_CREATED(\"player.signCreated\"),\n    PLAYER_SIGN_COPIED(\"player.signCopied\"),\n    PLAYER_TIME_LEFT(\"player.timeLeft\"),\n    PLAYER_TIME_KICK(\"player.timeKick\"),\n    PLAYER_TREASURES(\"player.treasures\"),\n    PLAYER_UNLIMITED_LIVES(\"player.unlimitedLives\"),\n    PLAYER_WAIT_FOR_OTHER_PLAYERS(\"player.waitForOtherPlayers\"),\n    REQUIREMENT_FEE(\"requirement.fee\"),\n    REQUIREMENT_FEE_ITEMS(\"requirement.feeItems\"),\n    REQUIREMENT_FEE_LEVEL(\"requirement.feeLevel\"),\n    REQUIREMENT_FEE_MONEY(\"requirement.feeMoney\"),\n    REQUIREMENT_FINISHED_DUNGEONS_AND(\"requirement.finishedDungeons.and\"),\n    REQUIREMENT_FINISHED_DUNGEONS_NAME(\"requirement.finishedDungeons.name\"),\n    REQUIREMENT_FINISHED_DUNGEONS_OR(\"requirement.finishedDungeons.or\"),\n    REQUIREMENT_FINISHED_DUNGEONS_WITHIN_TIME(\"requirement.finishedDungeons.withinTime\"),\n    REQUIREMENT_FORBIDDEN_ITEMS(\"requirement.forbiddenItems\"),\n    REQUIREMENT_GROUP_SIZE(\"requirement.groupSize\"),\n    REQUIREMENT_KEY_ITEMS(\"requirement.keyItems\"),\n    REQUIREMENT_PERMISSION(\"requirement.permission\"),\n    REQUIREMENT_TIME_SINCE_NEVER(\"requirement.timeSince.never\"),\n    REQUIREMENT_TIME_SINCE_FINISH(\"requirement.timeSince.finish\"),\n    REQUIREMENT_TIME_SINCE_START(\"requirement.timeSince.start\"),\n    REQUIREMENT_TIMEFRAME(\"requirement.timeframe\"),\n    REWARD_GENERAL(\"reward.general\"),\n    SIGN_END(\"sign.end\"),\n    SIGN_FLOOR_1(\"sign.floor.1\"),\n    SIGN_FLOOR_2(\"sign.floor.2\"),\n    SIGN_GLOBAL_FULL(\"sign.global.full\"),\n    SIGN_GLOBAL_IS_PLAYING(\"sign.global.isPlaying\"),\n    SIGN_GLOBAL_JOIN_GAME(\"sign.global.joinGame\"),\n    SIGN_GLOBAL_JOIN_GROUP(\"sign.global.joinGroup\"),\n    SIGN_GLOBAL_NEW_GAME(\"sign.global.newGame\"),\n    SIGN_GLOBAL_NEW_GROUP(\"sign.global.newGroup\"),\n    SIGN_LEAVE(\"sign.leave\"),\n    SIGN_READY(\"sign.ready\"),\n    SIGN_RESOURCE_PACK(\"sign.resourcePack\"),\n    SIGN_WAVE_1(\"sign.wave.1\"),\n    SIGN_WAVE_2(\"sign.wave.2\");\n\n    private String path;\n\n    DMessage(String path) {\n        this.path = path;\n    }\n\n    @Override\n    public String getPath() {\n        return path;\n    }\n\n    @Override\n    public MessageHandler getMessageHandler() {\n        return DungeonsXL.getInstance().getInitializer().getMessageHandler();\n    }\n\n    @Override\n    public void debug() {\n        MessageUtil.log(DungeonsXL.getInstance(), getMessage());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/config/MainConfig.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.config;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup.Color;\nimport static de.erethon.dungeonsxl.api.player.PlayerGroup.Color.*;\nimport de.erethon.dungeonsxl.world.WorldConfig;\nimport de.erethon.xlib.config.DREConfig;\nimport de.erethon.xlib.util.EnumUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * Represents the main config.yml.\n *\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class MainConfig extends DREConfig {\n\n    private DungeonsXL plugin;\n\n    public enum BackupMode {\n        ON_DISABLE,\n        ON_DISABLE_AND_SAVE,\n        ON_SAVE,\n        NEVER\n    }\n\n    public static final int CONFIG_VERSION = 21;\n\n    private String language = \"english\";\n    private boolean updaterEnabled = true;\n    private boolean enableEconomy = false;\n    private boolean groupAdaptersEnabled = false;\n\n    /* Chat */\n    private boolean chatEnabled = true;\n    private String chatFormatEdit = \"&2[Edit] &r%player_name%: \";\n    private String chatFormatGame = \"&2[Game] %group_color%%player_name%: &r\";\n    private String chatFormatGroup = \"&2%group_color%[%group_name%] %player_name%: &r\";\n    private String chatFormatSpy = \"&2[Chat Spy] %player_name%: &r\";\n\n    /* Tutorial */\n    private boolean tutorialActivated = false;\n    private String tutorialDungeonName = \"tutorial\";\n    private Dungeon tutorialDungeon;\n    private String tutorialStartGroup = \"default\";\n    private String tutorialEndGroup = \"player\";\n\n    /* Announcers */\n    private List<Color> groupColorPriority = new ArrayList<>(Arrays.asList(\n            DARK_BLUE,\n            LIGHT_RED,\n            YELLOW,\n            LIGHT_GREEN,\n            PURPLE,\n            ORANGE,\n            BLACK,\n            LIGHT_BLUE,\n            DARK_GREEN,\n            DARK_RED,\n            LIGHT_GRAY,\n            CYAN,\n            MAGENTA,\n            DARK_GRAY,\n            PINK\n    ));\n    private double announcementInterval = 30;\n\n    /* Misc */\n    private boolean sendFloorTitle = true;\n    private boolean globalDeathMessagesDisabled = true;\n    private Map<String, Object> externalMobProviders = new HashMap<>();\n    private Map<String, Object> resourcePacks = new HashMap<>();\n\n    /* Performance */\n    private int maxInstances = 10;\n    private int editInstanceRemovalDelay = 5;\n    private boolean strictMovementCheckEnabled = true;\n\n    /* Secure Mode */\n    private boolean secureModeEnabled = false;\n    private double secureModeCheckInterval = 5;\n    private boolean openInventories = false;\n    private boolean dropItems = false;\n    private List<String> editCommandWhitelist;\n    private BackupMode backupMode = BackupMode.ON_DISABLE_AND_SAVE;\n    private boolean lobbyContainersEnabled = false;\n\n    /* Permissions bridge */\n    private List<String> editPermissions;\n\n    private WorldConfig defaultWorldConfig;\n\n    public MainConfig(DungeonsXL plugin, File file) {\n        super(file, CONFIG_VERSION);\n\n        this.plugin = plugin;\n\n        if (initialize) {\n            initialize();\n        }\n        load();\n    }\n\n    public String getLanguage() {\n        return language;\n    }\n\n    public void setLanguage(String language) {\n        this.language = language;\n    }\n\n    public boolean isUpdaterEnabled() {\n        return updaterEnabled;\n    }\n\n    public void setUpdaterEnabled(boolean enabled) {\n        updaterEnabled = enabled;\n    }\n\n    public boolean isEconomyEnabled() {\n        return enableEconomy;\n    }\n\n    public void setEconomyEnabled(boolean enabled) {\n        enableEconomy = enabled;\n    }\n\n    public boolean areGroupAdaptersEnabled() {\n        return groupAdaptersEnabled;\n    }\n\n    public void setGroupAdaptersEnabled(boolean enabled) {\n        groupAdaptersEnabled = enabled;\n    }\n\n    public boolean isChatEnabled() {\n        return chatEnabled;\n    }\n\n    public void setChatEnabled(boolean enabled) {\n        chatEnabled = enabled;\n    }\n\n    public String getChatFormatEdit() {\n        return chatFormatEdit;\n    }\n\n    public void setEditFormatEdit(String string) {\n        chatFormatEdit = string;\n    }\n\n    public String getChatFormatGame() {\n        return chatFormatGame;\n    }\n\n    public void setChatFormatGame(String string) {\n        chatFormatGame = string;\n    }\n\n    public String getChatFormatGroup() {\n        return chatFormatGroup;\n    }\n\n    public void setChatFormatGroup(String string) {\n        chatFormatGroup = string;\n    }\n\n    public String getChatFormatSpy() {\n        return chatFormatSpy;\n    }\n\n    public void setChatFormatSpy(String string) {\n        chatFormatSpy = string;\n    }\n\n    public boolean isTutorialActivated() {\n        return tutorialActivated;\n    }\n\n    public void setTutorialActivated(boolean activated) {\n        tutorialActivated = activated;\n    }\n\n    public Dungeon getTutorialDungeon() {\n        if (tutorialDungeon == null) {\n            tutorialDungeon = plugin.getDungeonRegistry().get(tutorialDungeonName);\n        }\n        return tutorialDungeon;\n    }\n\n    public void setTutorialDungeon(Dungeon dungeon) {\n        tutorialDungeon = dungeon;\n    }\n\n    public String getTutorialStartGroup() {\n        return tutorialStartGroup;\n    }\n\n    public void setTutorialStartGroup(String group) {\n        tutorialStartGroup = group;\n    }\n\n    public String getTutorialEndGroup() {\n        return tutorialEndGroup;\n    }\n\n    public void setTutorialEndGroup(String group) {\n        tutorialEndGroup = group;\n    }\n\n    public List<Color> getGroupColorPriority() {\n        return groupColorPriority;\n    }\n\n    public Color getGroupColorPriority(int count) {\n        return (count < groupColorPriority.size() && count >= 0) ? groupColorPriority.get(count) : Color.WHITE;\n    }\n\n    public void setGroupColorPriority(List<Color> colors) {\n        groupColorPriority = colors;\n    }\n\n    public long getAnnouncmentInterval() {\n        return (long) (announcementInterval * 20);\n    }\n\n    public void setAnnouncementInterval(double interval) {\n        announcementInterval = interval;\n    }\n\n    public boolean areGlobalDeathMessagesDisabled() {\n        return globalDeathMessagesDisabled;\n    }\n\n    public void setGlobalDeathMessagesDisabled(boolean disabled) {\n        globalDeathMessagesDisabled = false;\n    }\n\n    public boolean isSendFloorTitleEnabled() {\n        return sendFloorTitle;\n    }\n\n    public void setSendFloorTitleEnabled(boolean enabled) {\n        sendFloorTitle = enabled;\n    }\n\n    public Map<String, Object> getExternalMobProviders() {\n        return externalMobProviders;\n    }\n\n    public Map<String, Object> getResourcePacks() {\n        return resourcePacks;\n    }\n\n    public int getMaxInstances() {\n        return maxInstances;\n    }\n\n    public void setMaxInstances(int maxInstances) {\n        this.maxInstances = maxInstances;\n    }\n\n    public int getEditInstanceRemovalDelay() {\n        return editInstanceRemovalDelay;\n    }\n\n    public void setEditInstanceRemovalDelay(int delay) {\n        editInstanceRemovalDelay = delay;\n    }\n\n    public boolean isStrictMovementCheckEnabled() {\n        return strictMovementCheckEnabled;\n    }\n\n    public void setStrictMovementCheckEnabled(boolean enabled) {\n        strictMovementCheckEnabled = enabled;\n    }\n\n    public boolean isSecureModeEnabled() {\n        return secureModeEnabled;\n    }\n\n    public void setSecureModeEnabled(boolean enabled) {\n        secureModeEnabled = enabled;\n    }\n\n    public boolean getOpenInventories() {\n        return openInventories && secureModeEnabled;\n    }\n\n    public void setOpenInventories(boolean openInventories) {\n        this.openInventories = openInventories;\n    }\n\n    /**\n     * @return if players may drop items while editing; false if secure mode disabled\n     */\n    public boolean getDropItems() {\n        return dropItems && secureModeEnabled;\n    }\n\n    /**\n     * @param dropItems if items may be dropped in edit mode\n     */\n    public void setDropItems(boolean dropItems) {\n        this.dropItems = dropItems;\n    }\n\n    public long getSecureModeCheckInterval() {\n        return (long) (secureModeCheckInterval * 20);\n    }\n\n    public void setSecureModeCheckInterval(double interval) {\n        secureModeCheckInterval = interval;\n    }\n\n    public List<String> getEditCommandWhitelist() {\n        return editCommandWhitelist;\n    }\n\n    public BackupMode getBackupMode() {\n        return backupMode;\n    }\n\n    public void setBackupMode(BackupMode mode) {\n        backupMode = mode;\n    }\n\n    public boolean areLobbyContainersEnabled() {\n        return lobbyContainersEnabled;\n    }\n\n    public void setLobbyContainersEnabled(boolean enabled) {\n        lobbyContainersEnabled = enabled;\n    }\n\n    public List<String> getEditPermissions() {\n        return editPermissions;\n    }\n\n    public WorldConfig getDefaultWorldConfig() {\n        return defaultWorldConfig;\n    }\n\n    @Override\n    public void initialize() {\n        /* Main Config */\n        if (!config.contains(\"language\")) {\n            config.set(\"language\", language);\n        }\n        if (!config.contains(\"updaterEnabled\")) {\n            config.set(\"updaterEnabled\", updaterEnabled);\n        }\n\n        if (!config.contains(\"enableEconomy\")) {\n            config.set(\"enableEconomy\", enableEconomy);\n        }\n\n        if (!config.contains(\"groupAdaptersEnabled\")) {\n            config.set(\"groupAdaptersEnabled\", groupAdaptersEnabled);\n        }\n\n        if (!config.contains(\"chatEnabled\")) {\n            config.set(\"chatEnabled\", chatEnabled);\n        }\n\n        if (!config.contains(\"chatFormat.edit\")) {\n            config.set(\"chatFormat.edit\", chatFormatEdit);\n        }\n\n        if (!config.contains(\"chatFormat.game\")) {\n            config.set(\"chatFormat.game\", chatFormatGame);\n        }\n\n        if (!config.contains(\"chatFormat.group\")) {\n            config.set(\"chatFormat.group\", chatFormatGroup);\n        }\n\n        if (!config.contains(\"chatFormat.spy\")) {\n            config.set(\"chatFormat.spy\", chatFormatSpy);\n        }\n\n        if (!config.contains(\"tutorial.activated\")) {\n            config.set(\"tutorial.activated\", tutorialActivated);\n        }\n\n        if (!config.contains(\"tutorial.dungeon\")) {\n            config.set(\"tutorial.dungeon\", tutorialDungeonName);\n        }\n\n        if (!config.contains(\"tutorial.startGroup\")) {\n            config.set(\"tutorial.startGroup\", tutorialStartGroup);\n        }\n\n        if (!config.contains(\"tutorial.endGroup\")) {\n            config.set(\"tutorial.endgroup\", tutorialEndGroup);\n        }\n\n        if (!config.contains(\"groupColorPriority\")) {\n            ArrayList<String> strings = new ArrayList<>();\n            for (Color color : groupColorPriority) {\n                strings.add(color.toString());\n            }\n            config.set(\"groupColorPriority\", strings);\n        }\n\n        if (!config.contains(\"announcementInterval\")) {\n            config.set(\"announcementInterval\", announcementInterval);\n        }\n\n        if (!config.contains(\"sendFloorTitle\")) {\n            config.set(\"sendFloorTitle\", sendFloorTitle);\n        }\n\n        if (!config.contains(\"globalDeathMessagesDisabled\")) {\n            config.set(\"globalDeathMessagesDisabled\", globalDeathMessagesDisabled);\n        }\n\n        if (!config.contains(\"externalMobProviders\")) {\n            config.createSection(\"externalMobProviders\");\n        }\n\n        if (!config.contains(\"resourcePacks\")) {\n            config.createSection(\"resourcePacks\");\n        }\n\n        if (!config.contains(\"maxInstances\")) {\n            config.set(\"maxInstances\", maxInstances);\n        }\n\n        if (!config.contains(\"editInstanceRemovalDelay\")) {\n            config.set(\"editInstanceRemovalDelay\", editInstanceRemovalDelay);\n        }\n\n        if (!config.contains(\"strictMovementCheckEnabled\")) {\n            config.set(\"strictMovementCheckEnabled\", strictMovementCheckEnabled);\n        }\n\n        if (!config.contains(\"secureMode.enabled\")) {\n            config.set(\"secureMode.enabled\", secureModeEnabled);\n        }\n\n        if (!config.contains(\"secureMode.openInventories\")) {\n            config.set(\"secureMode.openInventories\", openInventories);\n        }\n\n        if (!config.contains(\"secureMode.dropItems\")) {\n            config.set(\"secureMode.dropItems\", dropItems);\n        }\n\n        if (!config.contains(\"secureMode.checkInterval\")) {\n            config.set(\"secureMode.checkInterval\", secureModeCheckInterval);\n        }\n\n        if (!config.contains(\"secureMode.editCommandWhitelist\")) {\n            config.set(\"secureMode.editCommandWhitelist\", editCommandWhitelist);\n        }\n\n        if (!config.contains(\"backupMode\")) {\n            config.set(\"backupMode\", backupMode.toString());\n        }\n\n        if (!config.contains(\"lobbyContainersEnabled\")) {\n            config.set(\"lobbyContainersEnabled\", lobbyContainersEnabled);\n        }\n\n        if (!config.contains(\"editPermissions\")) {\n            config.set(\"editPermissions\", editPermissions);\n        }\n\n        /* Default Dungeon Config */\n        if (!config.contains(\"default\")) {\n            ConfigurationSection section = config.createSection(\"default\");\n            section.set(\"damageProtectedEntities\", Arrays.asList(\"ARMOR_STAND\", \"ITEM_FRAME\", \"PAINTING\"));\n            section.set(\"interactionProtectedEntities\", Arrays.asList(\"ARMOR_STAND\", \"ITEM_FRAME\"));\n        }\n\n        save();\n    }\n\n    @Override\n    public void load() {\n        language = config.getString(\"language\", language);\n        plugin.getInitializer().getMessageHandler().setDefaultLanguage(language);\n        updaterEnabled = config.getBoolean(\"updaterEnabled\", updaterEnabled);\n        enableEconomy = config.getBoolean(\"enableEconomy\", enableEconomy);\n        groupAdaptersEnabled = config.getBoolean(\"groupAdaptersEnabled\", groupAdaptersEnabled);\n        chatEnabled = config.getBoolean(\"chatEnabled\", chatEnabled);\n        chatFormatEdit = config.getString(\"chatFormat.edit\", chatFormatEdit);\n        chatFormatGame = config.getString(\"chatFormat.game\", chatFormatGame);\n        chatFormatGroup = config.getString(\"chatFormat.group\", chatFormatGroup);\n        chatFormatSpy = config.getString(\"chatFormat.spy\", chatFormatSpy);\n        chatEnabled = config.getBoolean(\"chatEnabled\", chatEnabled);\n        tutorialActivated = config.getBoolean(\"tutorial.activated\", tutorialActivated);\n        tutorialDungeonName = config.getString(\"tutorial.dungeon\", tutorialDungeonName);\n        tutorialStartGroup = config.getString(\"tutorial.startgroup\", tutorialStartGroup);\n        tutorialEndGroup = config.getString(\"tutorial.endgroup\", tutorialEndGroup);\n\n        if (config.getStringList(\"groupColorPriority\").size() < 14) {\n            ArrayList<String> strings = new ArrayList<>();\n            for (Color color : groupColorPriority) {\n                strings.add(color.toString());\n            }\n            config.set(\"groupColorPriority\", strings);\n            try {\n                config.save(file);\n            } catch (IOException exception) {\n            }\n\n        } else {\n            groupColorPriority.clear();\n            for (String color : config.getStringList(\"groupColorPriority\")) {\n                Color dColor = EnumUtil.getEnum(Color.class, color);\n                if (dColor != null && dColor != Color.WHITE) {\n                    groupColorPriority.add(dColor);\n                }\n            }\n        }\n\n        announcementInterval = config.getDouble(\"announcementInterval\", announcementInterval);\n        sendFloorTitle = config.getBoolean(\"sendFloorTitle\", sendFloorTitle);\n        globalDeathMessagesDisabled = config.getBoolean(\"globalDeathMessagesDisabled\", globalDeathMessagesDisabled);\n\n        ConfigurationSection externalMobProvidersSection = config.getConfigurationSection(\"externalMobProviders\");\n        if (externalMobProvidersSection != null) {\n            externalMobProviders = externalMobProvidersSection.getValues(false);\n        }\n        ConfigurationSection resourcePacksSection = config.getConfigurationSection(\"resourcePacks\");\n        if (resourcePacksSection != null) {\n            resourcePacks = resourcePacksSection.getValues(false);\n        }\n\n        maxInstances = config.getInt(\"maxInstances\", maxInstances);\n        editInstanceRemovalDelay = config.getInt(\"editInstanceRemovalDelay\", editInstanceRemovalDelay);\n        strictMovementCheckEnabled = config.getBoolean(\"strictMovementCheckEnabled\", strictMovementCheckEnabled);\n        secureModeEnabled = config.getBoolean(\"secureMode.enabled\", secureModeEnabled);\n        openInventories = config.getBoolean(\"secureMode.openInventories\", openInventories);\n        dropItems = config.getBoolean(\"secureMode.dropItems\", dropItems);\n        secureModeCheckInterval = config.getDouble(\"secureMode.checkInterval\", secureModeCheckInterval);\n        editCommandWhitelist = config.getStringList(\"secureMode.editCommandWhitelist\");\n\n        String mode = config.getString(\"backupMode\");\n        if (EnumUtil.isValidEnum(BackupMode.class, mode)) {\n            backupMode = BackupMode.valueOf(mode);\n        }\n        lobbyContainersEnabled = config.getBoolean(\"lobbyContainersEnabled\", lobbyContainersEnabled);\n\n        editPermissions = config.getStringList(\"editPermissions\");\n\n        ConfigurationSection defaultWorldSection = config.getConfigurationSection(\"default\");\n        if (defaultWorldSection != null) {\n            defaultWorldConfig = new WorldConfig(plugin, defaultWorldSection);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/dungeon/DDungeon.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.dungeon;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * @author Daniel Saukel\n */\npublic class DDungeon implements Dungeon {\n\n    private DungeonsXL plugin;\n\n    private String name;\n    private DungeonConfig config;\n    private ResourceWorld map;\n    private GameRuleContainer rules;\n\n    /**\n     * Artificial dungeon\n     *\n     * @param plugin   the plugin instance\n     * @param resource the only resource world\n     */\n    public DDungeon(DungeonsXL plugin, ResourceWorld resource) {\n        this.plugin = plugin;\n\n        name = resource.getName();\n        map = resource;\n        setupRules();\n    }\n\n    private DDungeon() {\n    }\n\n    /**\n     * Real dungeon\n     *\n     * @param plugin the plugin instance\n     * @param file   the file to load from\n     * @return the dungeon or null if the config is erroneous\n     */\n    public static Dungeon create(DungeonsXL plugin, File file) {\n        DungeonConfig config = new DungeonConfig(plugin, file);\n        if (config.getStartFloor() == null || config.getEndFloor() == null) {\n            return null;\n        }\n\n        DDungeon dungeon = new DDungeon();\n        dungeon.plugin = plugin;\n        dungeon.name = file.getName().replaceAll(\".yml\", \"\");\n        dungeon.config = config;\n        dungeon.map = config.getStartFloor();\n        if (dungeon.isSetupCorrect()) {\n            dungeon.setupRules();\n            return dungeon;\n        } else {\n            return null;\n        }\n    }\n\n    public DungeonConfig getConfig() {\n        if (!isMultiFloor()) {\n            throw new IllegalStateException(\"Tried to access the dungeon config of a single floor dungeon\");\n        }\n        return config;\n    }\n\n    @Override\n    public String getName() {\n        return name;\n    }\n\n    @Override\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public boolean isMultiFloor() {\n        return config != null;\n    }\n\n    @Override\n    public ResourceWorld getStartFloor() {\n        return map;\n    }\n\n    @Override\n    public void setStartFloor(ResourceWorld startFloor) {\n        getConfig().setStartFloor(startFloor);\n    }\n\n    @Override\n    public ResourceWorld getEndFloor() {\n        return getConfig().getEndFloor();\n    }\n\n    @Override\n    public void setEndFloor(ResourceWorld endFloor) {\n        getConfig().setEndFloor(endFloor);\n    }\n\n    @Override\n    public List<ResourceWorld> getFloors() {\n        if (isMultiFloor()) {\n            return new ArrayList<>(getConfig().getFloors());\n        } else {\n            return new ArrayList<>(Arrays.asList(map));\n        }\n    }\n\n    @Override\n    public void addFloor(ResourceWorld resource) {\n        getConfig().addFloor(resource);\n    }\n\n    @Override\n    public void removeFloor(ResourceWorld resource) {\n        getConfig().removeFloor(resource);\n    }\n\n    @Override\n    public int getFloorCount() {\n        return getConfig().getFloorCount();\n    }\n\n    @Override\n    public void setFloorCount(int floorCount) {\n        getConfig().setFloorCount(floorCount);\n    }\n\n    @Override\n    public boolean getRemoveWhenPlayed() {\n        return getConfig().getRemoveWhenPlayed();\n    }\n\n    @Override\n    public void setRemoveWhenPlayed(boolean removeWhenPlayed) {\n        getConfig().setRemoveWhenPlayed(removeWhenPlayed);\n    }\n\n    @Override\n    public GameRuleContainer getOverrideValues() {\n        return getConfig().getOverrideValues();\n    }\n\n    @Override\n    public GameRuleContainer getDefaultValues() {\n        return getConfig().getDefaultValues();\n    }\n\n    @Override\n    public GameRuleContainer getRules() {\n        return rules;\n    }\n\n    @Override\n    public void setRules(GameRuleContainer rules) {\n        this.rules = rules;\n    }\n\n    @Override\n    public void setupRules() {\n        if (rules != null) {\n            return;\n        }\n        if (isMultiFloor()) {\n            rules = new GameRuleContainer(getOverrideValues());\n            if (map.getRules() != null) {\n                rules.merge(map.getRules());\n            }\n            rules.merge(getDefaultValues());\n        } else if (map.getRules() != null) {\n            rules = new GameRuleContainer(map.getRules());\n        } else {\n            rules = new GameRuleContainer();\n        }\n        rules.merge(plugin.getMainConfig().getDefaultWorldConfig());\n        rules.merge(GameRule.DEFAULT_VALUES);\n    }\n\n    @Override\n    public boolean isSetupCorrect() {\n        for (ResourceWorld resource : plugin.getMapRegistry()) {\n            if (resource.getName().equals(name)) {\n                return false;\n            }\n        }\n        return getConfig() == null || (getConfig().getStartFloor() != null && getConfig().getEndFloor() != null);\n    }\n\n    /* Statics */\n    public static File getFileFromName(String name) {\n        return new File(DungeonsXL.DUNGEONS, name + \".yml\");\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{name=\" + name + \"; multiFloor=\" + isMultiFloor() + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/dungeon/DGame.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.dungeon;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameGoal;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.global.GameSign;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.sign.windup.MobSign;\nimport de.erethon.dungeonsxl.trigger.ProgressTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class DGame implements Game {\n\n    private DungeonsXL plugin;\n\n    private Dungeon dungeon;\n    private GameWorld world;\n    private List<ResourceWorld> unplayedFloors = new ArrayList<>();\n    private ResourceWorld nextFloor;\n    private int floorCount;\n    private List<PlayerGroup> groups = new ArrayList<>();\n    private boolean tutorial;\n    private boolean rewards = true;\n    private boolean started;\n    private int waveCount;\n    private Map<String, Integer> gameKills = new HashMap<>();\n    private Map<String, Integer> waveKills = new HashMap<>();\n\n    public DGame(DungeonsXL plugin, Dungeon dungeon) {\n        this.plugin = plugin;\n        plugin.getGameCache().add(this);\n\n        setDungeon(dungeon);\n\n        if (this.dungeon == null) {\n            throw new IllegalStateException(\"Game initialized without dungeon\");\n        }\n        tutorial = false;\n        started = false;\n    }\n\n    public DGame(DungeonsXL plugin, Dungeon dungeon, PlayerGroup group) {\n        this(plugin, dungeon);\n        addGroup(group);\n    }\n\n    public DGame(DungeonsXL plugin, Dungeon dungeon, List<PlayerGroup> groups) {\n        this(plugin, dungeon);\n        groups.forEach(this::addGroup);\n    }\n\n    @Override\n    public boolean isTutorial() {\n        return tutorial;\n    }\n\n    @Override\n    public void setTutorial(boolean tutorial) {\n        this.tutorial = tutorial;\n    }\n\n    @Override\n    public List<PlayerGroup> getGroups() {\n        return new ArrayList<>(groups);\n    }\n\n    @Override\n    public void addGroup(PlayerGroup group) {\n        groups.add(group);\n\n        ((DGroup) group).setGame(this);\n        group.setInitialLives(getRules().getState(GameRule.INITIAL_GROUP_LIVES));\n        group.setLives(getRules().getState(GameRule.INITIAL_GROUP_LIVES));\n        GameGoal goal = getRules().getState(GameRule.GAME_GOAL);\n        if (goal.getType().hasComponent(GameGoal.INITIAL_SCORE)) {\n            group.setScore(goal.getState(GameGoal.INITIAL_SCORE));\n        }\n    }\n\n    @Override\n    public void removeGroup(PlayerGroup group) {\n        groups.remove(group);\n\n        if (groups.isEmpty()) {\n            delete();\n        }\n    }\n\n    @Override\n    public boolean hasStarted() {\n        return started;\n    }\n\n    @Override\n    public void setStarted(boolean started) {\n        this.started = started;\n    }\n\n    @Override\n    public Dungeon getDungeon() {\n        return dungeon;\n    }\n\n    /**\n     * Sets up all dungeon-related fields.\n     *\n     * @param dungeon the dungeon to set\n     */\n    public void setDungeon(Dungeon dungeon) {\n        this.dungeon = dungeon;\n        if (dungeon.isMultiFloor()) {\n            unplayedFloors = dungeon.getFloors();\n        }\n    }\n\n    /**\n     * Sets up all dungeon-related fields.\n     *\n     * @param name the name of the dungeon\n     * @return if the action was successful\n     */\n    public boolean setDungeon(String name) {\n        dungeon = plugin.getDungeonRegistry().get(name);\n        if (dungeon != null) {\n            unplayedFloors = dungeon.getFloors();\n            return true;\n\n        } else {\n            ResourceWorld resource = plugin.getMapRegistry().get(name);\n            if (resource != null) {\n                dungeon = resource.getSingleFloorDungeon();\n                return true;\n            }\n            return false;\n        }\n    }\n\n    @Override\n    public DGameWorld getWorld() {\n        return (DGameWorld) world;\n    }\n\n    @Override\n    public void setWorld(GameWorld gameWorld) {\n        world = gameWorld;\n    }\n\n    @Override\n    public List<ResourceWorld> getUnplayedFloors() {\n        return unplayedFloors;\n    }\n\n    @Override\n    public boolean addUnplayedFloor(ResourceWorld unplayedFloor) {\n        return unplayedFloors.add(unplayedFloor);\n    }\n\n    @Override\n    public boolean removeUnplayedFloor(ResourceWorld unplayedFloor, boolean force) {\n        if (getDungeon().getRemoveWhenPlayed() || force) {\n            return unplayedFloors.remove(unplayedFloor);\n        }\n        return false;\n    }\n\n    @Override\n    public ResourceWorld getNextFloor() {\n        return nextFloor;\n    }\n\n    @Override\n    public void setNextFloor(ResourceWorld floor) {\n        nextFloor = floor;\n    }\n\n    @Override\n    public int getFloorCount() {\n        return floorCount;\n    }\n\n    @Override\n    public boolean hasRewards() {\n        return rewards;\n    }\n\n    @Override\n    public void setRewards(boolean enabled) {\n        rewards = enabled;\n    }\n\n    /**\n     * @return the waveCount\n     */\n    public int getWaveCount() {\n        return waveCount;\n    }\n\n    /**\n     * @param waveCount the waveCount to set\n     */\n    public void setWaveCount(int waveCount) {\n        this.waveCount = waveCount;\n    }\n\n    /**\n     * @return how many mobs have been killed in the game\n     */\n    public int getGameKills() {\n        int count = 0;\n        for (String killer : gameKills.keySet()) {\n            count += gameKills.get(killer);\n        }\n        return count;\n    }\n\n    /**\n     * @return how many mobs have been killed in the last game\n     */\n    public int getWaveKills() {\n        int count = 0;\n        for (String killer : waveKills.keySet()) {\n            count += waveKills.get(killer);\n        }\n        return count;\n    }\n\n    /**\n     * @param killer the killer; null if the killer is not a player\n     */\n    public void addKill(String killer) {\n        if (killer == null) {\n            killer = \"N/A\";\n        }\n        waveKills.put(killer, waveKills.get(killer) == null ? 1 : waveKills.get(killer) + 1);\n    }\n\n    /**\n     * Adds the values of the wave kills map to the game kills map and resets the wave kills.\n     */\n    public void resetWaveKills() {\n        gameKills.putAll(waveKills);\n        waveKills.clear();\n    }\n\n    @Override\n    public Collection<Player> getPlayers() {\n        Set<Player> toReturn = new HashSet<>();\n        for (PlayerGroup group : groups) {\n            toReturn.addAll(group.getMembers().getOnlinePlayers());\n        }\n        return toReturn;\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return groups.isEmpty();\n    }\n\n    @Override\n    public GameWorld ensureWorldIsLoaded(boolean ignoreLimit) {\n        if (world != null) {\n            return world;\n        }\n        world = dungeon.getMap().instantiateGameWorld(this, ignoreLimit);\n        return world;\n    }\n\n    @Override\n    public boolean start() {\n        getWorld().setWeather(getRules());\n\n        for (PlayerGroup group : groups) {\n            if (group == null) {\n                continue;\n            }\n            if (!((DGroup) group).checkStartGame(this)) {\n                MessageUtil.debug(plugin, \"Could not start game for group \" + group);\n                return false;\n            }\n        }\n        int i = 0;\n        for (PlayerGroup group : groups) {\n            if (group != null) {\n                ((DGroup) group).startGame(this, i++);\n            }\n        }\n\n        if (getWorld() != null) {\n            if (!getWorld().isPlaying()) {\n                getWorld().startGame();\n            }\n        }\n\n        floorCount++;\n        nextFloor = null;\n        started = true;\n        return true;\n    }\n\n    @Override\n    public void delete() {\n        GameSign gameSign = GameSign.getByGame(plugin, this);\n\n        plugin.getGameCache().remove(this);\n\n        if (gameSign != null) {\n            gameSign.update();\n        }\n    }\n\n    /**\n     * @param mobCountIncreaseRate the new mob count will be increased by this rate\n     * @param teleport             whether or not to teleport the players to the start location\n     */\n    public void finishWave(final double mobCountIncreaseRate, final boolean teleport) {\n        waveCount++;\n        resetWaveKills();\n\n        for (Trigger uncasted : getWorld().getTriggers()) {\n            if (!(uncasted instanceof ProgressTrigger)) {\n                continue;\n            }\n            ProgressTrigger trigger = (ProgressTrigger) uncasted;\n            if (getWaveCount() >= trigger.getWaveCount() & getFloorCount() >= trigger.getFloorCount() - 1\n                    || !getUnplayedFloors().contains(trigger.getFloor()) & trigger.getFloor() != null) {\n                trigger.trigger(true, null);\n            }\n        }\n\n        int delay = getRules().getState(GameRule.TIME_TO_NEXT_WAVE);\n        sendMessage(DMessage.GROUP_WAVE_FINISHED.getMessage(String.valueOf(waveCount), String.valueOf(delay)));\n\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                if (teleport) {\n                    groups.forEach(g -> g.getMembers().getOnlinePlayers().forEach(p -> p.teleport(world.getStartLocation(plugin.getPlayerGroup(p)))));\n                }\n\n                for (DungeonSign dSign : world.getDungeonSigns()) {\n                    if (!(dSign instanceof MobSign)) {\n                        continue;\n                    }\n\n                    MobSign mobSign = (MobSign) dSign;\n                    int newAmount = (int) Math.ceil(mobSign.getInitialAmount() * mobCountIncreaseRate);\n\n                    mobSign.setN(newAmount);\n                    mobSign.startTask();\n                }\n            }\n        }.runTaskLater(plugin, delay * 20);\n    }\n\n    @Override\n    public boolean isFinished() {\n        return groups.stream().allMatch(PlayerGroup::isFinished);\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{dungeon=\" + getDungeon() + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/dungeon/DungeonConfig.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.dungeon;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.world.WorldConfig;\nimport de.erethon.xlib.config.DREConfig;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Represents a dungeon script. See {@link de.erethon.dungeonsxl.dungeon.DDungeon}.\n *\n * @author Daniel Saukel\n */\npublic class DungeonConfig extends DREConfig {\n\n    private DungeonsXL plugin;\n\n    public static final int CONFIG_VERSION = 1;\n\n    private ResourceWorld startFloor;\n    private ResourceWorld endFloor;\n    private List<ResourceWorld> floors = new ArrayList<>();\n    private int floorCount;\n    private boolean removeWhenPlayed;\n    private WorldConfig overrideValues;\n    private WorldConfig defaultValues;\n\n    public DungeonConfig(DungeonsXL plugin, File file) {\n        super(file, CONFIG_VERSION);\n\n        this.plugin = plugin;\n\n        if (initialize) {\n            initialize();\n        }\n        load();\n    }\n\n    public ResourceWorld getStartFloor() {\n        return startFloor;\n    }\n\n    public void setStartFloor(ResourceWorld startFloor) {\n        this.startFloor = startFloor;\n    }\n\n    public ResourceWorld getEndFloor() {\n        return endFloor;\n    }\n\n    public void setEndFloor(ResourceWorld endFloor) {\n        this.endFloor = endFloor;\n    }\n\n    public List<ResourceWorld> getFloors() {\n        return floors;\n    }\n\n    public void addFloor(ResourceWorld resource) {\n        floors.add(resource);\n    }\n\n    public void removeFloor(ResourceWorld resource) {\n        floors.remove(resource);\n    }\n\n    public int getFloorCount() {\n        return floorCount;\n    }\n\n    public void setFloorCount(int floorCount) {\n        this.floorCount = floorCount;\n    }\n\n    public boolean getRemoveWhenPlayed() {\n        return removeWhenPlayed;\n    }\n\n    public void setRemoveWhenPlayed(boolean removeWhenPlayed) {\n        this.removeWhenPlayed = removeWhenPlayed;\n    }\n\n    public WorldConfig getOverrideValues() {\n        return overrideValues;\n    }\n\n    public WorldConfig getDefaultValues() {\n        return defaultValues;\n    }\n\n    public boolean containsFloor(ResourceWorld resource) {\n        return floors.contains(resource) || startFloor.equals(resource) || endFloor.equals(resource);\n    }\n\n    public boolean containsFloor(String mapName) {\n        return containsFloor(plugin.getMapRegistry().get(mapName));\n    }\n\n    @Override\n    public void load() {\n        for (String floor : config.getStringList(\"floors\")) {\n            ResourceWorld resource = plugin.getMapRegistry().get(floor);\n            if (resource != null) {\n                floors.add(resource);\n            }\n        }\n        startFloor = plugin.getMapRegistry().get(config.getString(\"startFloor\"));\n        endFloor = plugin.getMapRegistry().get(config.getString(\"endFloor\"));\n        floorCount = config.getInt(\"floorCount\", floors.size() + 2);\n        removeWhenPlayed = config.getBoolean(\"removeWhenPlayed\", removeWhenPlayed);\n\n        overrideValues = new WorldConfig(plugin, config.getConfigurationSection(\"overrideValues\"));\n        defaultValues = new WorldConfig(plugin, config.getConfigurationSection(\"defaultValues\"));\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{file=\" + file.getPath() + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/DPortal.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.util.BlockUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport net.md_5.bungee.api.chat.ClickEvent;\nimport net.md_5.bungee.api.chat.TextComponent;\nimport org.bukkit.Location;\nimport org.bukkit.Material;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * A portal that leads into a dungeon.\n *\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DPortal extends GlobalProtection {\n\n    private Block block1;\n    private Block block2;\n    private ExItem material = VanillaItem.NETHER_PORTAL;\n    private boolean zAxis;\n    private boolean active;\n    private Set<Block> blocks;\n\n    public DPortal(DungeonsXL plugin, int id, World world, ExItem material, boolean active) {\n        super(plugin, world, id);\n\n        this.material = material;\n        this.active = active;\n    }\n\n    public DPortal(DungeonsXL plugin, World world, int id, ConfigurationSection config) {\n        super(plugin, world, id);\n\n        block1 = world.getBlockAt(config.getInt(\"loc1.x\"), config.getInt(\"loc1.y\"), config.getInt(\"loc1.z\"));\n        block2 = world.getBlockAt(config.getInt(\"loc2.x\"), config.getInt(\"loc2.y\"), config.getInt(\"loc2.z\"));\n        material = plugin.getXLib().getExItem(config.getString(\"material\"));\n        if (material == null) {\n            material = VanillaItem.NETHER_PORTAL;\n        }\n        String axis = config.getString(\"axis\");\n        zAxis = axis != null && axis.equalsIgnoreCase(\"z\");\n        active = true;\n        create(null);\n    }\n\n    /**\n     * @return the block1\n     */\n    public Block getBlock1() {\n        return block1;\n    }\n\n    /**\n     * @param block1 the block1 to set\n     */\n    public void setBlock1(Block block1) {\n        this.block1 = block1;\n    }\n\n    /**\n     * @return the block2\n     */\n    public Block getBlock2() {\n        return block2;\n    }\n\n    /**\n     * @param block2 the block2 to set\n     */\n    public void setBlock2(Block block2) {\n        this.block2 = block2;\n    }\n\n    /**\n     * @return if the portal is active\n     */\n    public boolean isActive() {\n        return active;\n    }\n\n    /**\n     * @param active set the DPortal active\n     */\n    public void setActive(boolean active) {\n        this.active = active;\n    }\n\n    /**\n     * Create a new DPortal\n     *\n     * @param player the creator\n     */\n    public void create(DGlobalPlayer player) {\n        if (block1 == null || block2 == null) {\n            delete();\n            return;\n        }\n\n        if (player != null && material == VanillaItem.NETHER_PORTAL) {\n            float yaw = player.getPlayer().getLocation().getYaw();\n            if (yaw >= 45 & yaw < 135 || yaw >= 225 & yaw < 315) {\n                zAxis = true;\n            } else if (yaw >= 315 | yaw < 45 || yaw >= 135 & yaw < 225) {\n                zAxis = false;\n            }\n        }\n\n        int x1 = block1.getX(), y1 = block1.getY(), z1 = block1.getZ();\n        int x2 = block2.getX(), y2 = block2.getY(), z2 = block2.getZ();\n        int xcount = 0, ycount = 0, zcount = 0;\n\n        if (x1 > x2) {\n            xcount = -1;\n        } else if (x1 < x2) {\n            xcount = 1;\n        }\n        if (y1 > y2) {\n            ycount = -1;\n        } else if (y1 < y2) {\n            ycount = 1;\n        }\n        if (z1 > z2) {\n            zcount = -1;\n        } else if (z1 < z2) {\n            zcount = 1;\n        }\n\n        int xx = x1;\n        do {\n            int yy = y1;\n\n            do {\n                int zz = z1;\n\n                do {\n                    Material type = getWorld().getBlockAt(xx, yy, zz).getType();\n                    if (!type.isSolid()) {\n                        Block block = getWorld().getBlockAt(xx, yy, zz);\n                        block.setType(material.getMaterial(), false);\n                        if (material == VanillaItem.NETHER_PORTAL) {\n                            DungeonsXL.BLOCK_ADAPTER.setAxis(block, zAxis);\n                        }\n                    }\n\n                    zz = zz + zcount;\n                } while (zz != z2 + zcount);\n\n                yy = yy + ycount;\n            } while (yy != y2 + ycount);\n\n            xx = xx + xcount;\n        } while (xx != x2 + xcount);\n\n        if (player == null) {\n            return;\n        }\n        player.getPlayer().getInventory().setItemInHand(player.getCachedItem());\n        player.setCachedItem(null);\n\n        if (material != VanillaItem.NETHER_PORTAL) {\n            player.sendMessage(DMessage.PLAYER_PORTAL_CREATED.getMessage());\n            player.setCreatingPortal(null);\n\n        } else {\n            ClickEvent onClickYes = new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/dungeonsxl portal \" + VanillaItem.NETHER_PORTAL.getId() + \" -rotate\");\n            TextComponent yes = new TextComponent(DMessage.BUTTON_ACCEPT.getMessage());\n            yes.setClickEvent(onClickYes);\n\n            ClickEvent onClickNo = new ClickEvent(ClickEvent.Action.RUN_COMMAND, \"/dungeonsxl portal \" + VanillaItem.NETHER_PORTAL.getId() + \" -norotate\");\n            TextComponent no = new TextComponent(DMessage.BUTTON_DENY.getMessage());\n            no.setClickEvent(onClickNo);\n\n            player.sendMessage(DMessage.PLAYER_PORTAL_ROTATE.getMessage());\n            player.getPlayer().spigot().sendMessage(yes, new TextComponent(\" \"), no);\n        }\n    }\n\n    public void rotate() {\n        zAxis = !zAxis;\n        for (Block block : getBlocks()) {\n            if (block.getType() == VanillaItem.NETHER_PORTAL.getMaterial()) {\n                DungeonsXL.BLOCK_ADAPTER.setAxis(block, zAxis);\n            }\n        }\n    }\n\n    /**\n     * @param player the player to teleport into his dungeon\n     */\n    public void teleport(Player player) {\n        if (plugin.isLoadingWorld()) {\n            return;\n        }\n\n        PlayerGroup group = plugin.getPlayerGroup(player);\n        if (group == null) {\n            MessageUtil.sendActionBarMessage(player, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n\n        Dungeon dungeon = group.getDungeon();\n        if (dungeon == null) {\n            MessageUtil.sendActionBarMessage(player, DMessage.ERROR_NO_SUCH_DUNGEON.getMessage());\n            return;\n        }\n\n        if (!plugin.getPlayerCache().get(player).checkRequirements(dungeon)) {\n            return;\n        }\n\n        Game game = group.getGame();\n        if (game == null) {\n            game = new DGame(plugin, dungeon, group);\n        }\n        GameWorld target = game.ensureWorldIsLoaded(false);\n        if (target == null) {\n            MessageUtil.sendActionBarMessage(player, DMessage.ERROR_TOO_MANY_INSTANCES.getMessage());\n            return;\n        }\n\n        new DGamePlayer(plugin, player, target);\n    }\n\n    @Override\n    public Set<Block> getBlocks() {\n        if (blocks == null) {\n            if (block1 != null && block2 != null) {\n                blocks = BlockUtil.getBlocksBetween(block1, block2);\n            } else {\n                blocks = new HashSet<>();\n            }\n        }\n\n        return blocks;\n    }\n\n    @Override\n    public String getDataPath() {\n        return \"protections.portals\";\n    }\n\n    @Override\n    public void save(ConfigurationSection config) {\n        if (!active) {\n            return;\n        }\n\n        config.set(\"loc1.x\", block1.getX());\n        config.set(\"loc1.y\", block1.getY());\n        config.set(\"loc1.z\", block1.getZ());\n\n        config.set(\"loc2.x\", block2.getX());\n        config.set(\"loc2.y\", block2.getY());\n        config.set(\"loc2.z\", block2.getZ());\n\n        config.set(\"material\", material.getId());\n        if (material == VanillaItem.NETHER_PORTAL) {\n            config.set(\"axis\", zAxis ? \"z\" : \"x\");\n        }\n    }\n\n    @Override\n    public void delete() {\n        plugin.getGlobalProtectionCache().removeProtection(this);\n\n        if (block1 == null || block2 == null) {\n            return;\n        }\n\n        int x1 = block1.getX(), y1 = block1.getY(), z1 = block1.getZ();\n        int x2 = block2.getX(), y2 = block2.getY(), z2 = block2.getZ();\n        int xcount = 0, ycount = 0, zcount = 0;\n\n        if (x1 > x2) {\n            xcount = -1;\n        } else if (x1 < x2) {\n            xcount = 1;\n        }\n\n        if (y1 > y2) {\n            ycount = -1;\n        } else if (y1 < y2) {\n            ycount = 1;\n        }\n\n        if (z1 > z2) {\n            zcount = -1;\n        } else if (z1 < z2) {\n            zcount = 1;\n        }\n\n        int xx = x1;\n        do {\n            int yy = y1;\n            do {\n                int zz = z1;\n                do {\n                    Material type = getWorld().getBlockAt(xx, yy, zz).getType();\n\n                    if (material.getMaterial() == type) {\n                        getWorld().getBlockAt(xx, yy, zz).setType(Material.AIR);\n                    }\n\n                    zz = zz + zcount;\n                } while (zz != z2 + zcount);\n\n                yy = yy + ycount;\n            } while (yy != y2 + ycount);\n\n            xx = xx + xcount;\n        } while (xx != x2 + xcount);\n    }\n\n    /* Statics */\n    /**\n     * @param plugin   the plugin instance\n     * @param location a location covered by the returned portal\n     * @return the portal at the location, null if there is none\n     */\n    public static DPortal getByLocation(DungeonsXL plugin, Location location) {\n        return getByBlock(plugin, location.getBlock());\n    }\n\n    /**\n     * @param plugin the plugin instance\n     * @param block  a block covered by the returned portal\n     * @return the portal that the block belongs to, null if it belongs to none\n     */\n    public static DPortal getByBlock(DungeonsXL plugin, Block block) {\n        if (plugin.isInstance(block.getWorld())) {\n            return null;\n        }\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(DPortal.class)) {\n            DPortal portal = (DPortal) protection;\n            if (!portal.getWorld().equals(block.getWorld()) || portal.getBlock1() == null || portal.getBlock2() == null) {\n                continue;\n            }\n\n            int x1 = portal.block1.getX(), y1 = portal.block1.getY(), z1 = portal.block1.getZ();\n            int x2 = portal.block2.getX(), y2 = portal.block2.getY(), z2 = portal.block2.getZ();\n            int x3 = block.getX(), y3 = block.getY(), z3 = block.getZ();\n\n            if (x1 > x2) {\n                if (x3 < x2 || x3 > x1) {\n                    continue;\n                }\n\n            } else if (x3 > x2 || x3 < x1) {\n                continue;\n            }\n\n            if (y1 > y2) {\n                if (y3 < y2 || y3 > y1) {\n                    continue;\n                }\n\n            } else if (y3 > y2 || y3 < y1) {\n                continue;\n            }\n\n            if (z1 > z2) {\n                if (z3 < z2 || z3 > z1) {\n                    continue;\n                }\n            } else if (z3 > z2 || z3 < z1) {\n                continue;\n            }\n\n            return portal;\n        }\n\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/GameSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.SignChangeEvent;\n\n/**\n * Basically a GroupSign, but to form a game of multiple groups.\n *\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class GameSign extends JoinSign {\n\n    public static final String GAME_SIGN_TAG = \"Game\";\n\n    private DGame game;\n\n    public GameSign(DungeonsXL plugin, int id, Block startSign, String identifier, int maxGroupsPerGame, int startIfElementsAtLeast) {\n        super(plugin, id, startSign, identifier, maxGroupsPerGame, startIfElementsAtLeast);\n    }\n\n    public GameSign(DungeonsXL plugin, World world, int id, ConfigurationSection config) {\n        super(plugin, world, id, config);\n    }\n\n    /**\n     * @return the attached game\n     */\n    public DGame getGame() {\n        return game;\n    }\n\n    /**\n     * @param game the game to set\n     */\n    public void setGame(DGame game) {\n        this.game = game;\n    }\n\n    /**\n     * Update this game sign to show the game(s) correctly.\n     */\n    @Override\n    public void update() {\n        if (!(startSign.getState() instanceof Sign)) {\n            return;\n        }\n\n        super.update();\n        Sign sign = (Sign) startSign.getState();\n\n        if (game == null || game.getGroups().isEmpty()) {\n            loadedWorld = false;\n            sign.setLine(0, DMessage.SIGN_GLOBAL_NEW_GAME.getMessage());\n            sign.update();\n            return;\n        }\n\n        if (game.getGroups().size() >= startIfElementsAtLeast && startIfElementsAtLeast != -1) {\n            loadedWorld = true;\n            game.getGroups().forEach(g -> ((DGroup) g).teleport());\n        }\n\n        if (game.hasStarted()) {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_IS_PLAYING.getMessage());\n\n        } else if (game.getGroups().size() >= maxElements) {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_FULL.getMessage());\n\n        } else {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_JOIN_GAME.getMessage());\n        }\n\n        int j = 1;\n        Sign rowSign = sign;\n\n        for (PlayerGroup dGroup : game.getGroups()) {\n            if (j > 3) {\n                j = 0;\n                rowSign = (Sign) sign.getBlock().getRelative(0, -1, 0).getState();\n            }\n\n            if (rowSign != null) {\n                rowSign.setLine(j, dGroup.getName());\n            }\n\n            j++;\n            rowSign.update();\n        }\n\n        sign.update();\n    }\n\n    @Override\n    public String getDataPath() {\n        return \"protections.gameSigns\";\n    }\n\n    public void onPlayerInteract(Block block, Player player) {\n        DGroup dGroup = (DGroup) plugin.getPlayerGroup(player);\n        if (dGroup == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_JOIN_GROUP.getMessage());\n            return;\n        }\n        if (!dGroup.getLeader().equals(player)) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NOT_LEADER.getMessage());\n            return;\n        }\n\n        if (dGroup.getGame() != null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_GAME.getMessage());\n            return;\n        }\n\n        Block topBlock = block.getRelative(0, startSign.getY() - block.getY(), 0);\n        if (!(topBlock.getState() instanceof Sign)) {\n            return;\n        }\n\n        Sign topSign = (Sign) topBlock.getState();\n\n        if (topSign.getLine(0).equals(DMessage.SIGN_GLOBAL_NEW_GAME.getMessage())) {\n            if (dungeon == null) {\n                MessageUtil.sendMessage(player, DMessage.ERROR_SIGN_WRONG_FORMAT.getMessage());\n                return;\n            }\n\n            dGroup.setDungeon(dungeon);\n            game = new DGame(plugin, dungeon, dGroup);\n            update();\n\n        } else if (topSign.getLine(0).equals(DMessage.SIGN_GLOBAL_JOIN_GAME.getMessage())) {\n            dGroup.setDungeon(dungeon);\n            game.addGroup(dGroup);\n            update();\n        }\n    }\n\n    /* Statics */\n    /**\n     * @param plugin the plugin instance\n     * @param block  a block which is protected by the returned GameSign\n     * @return the game sign the block belongs to, null if it belongs to none\n     */\n    public static GameSign getByBlock(DungeonsXL plugin, Block block) {\n        if (!Category.SIGNS.containsBlock(block)) {\n            return null;\n        }\n\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(GameSign.class)) {\n            GameSign gameSign = (GameSign) protection;\n            Block start = gameSign.startSign;\n            if (start == block || (start.getX() == block.getX() && start.getZ() == block.getZ() && (start.getY() >= block.getY() && start.getY() - gameSign.verticalSigns <= block.getY()))) {\n                return gameSign;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param plugin the plugin instance\n     * @param game   the game to check\n     * @return the game that this sign creates\n     */\n    public static GameSign getByGame(DungeonsXL plugin, DGame game) {\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(GameSign.class)) {\n            GameSign gameSign = (GameSign) protection;\n            if (gameSign.game == game) {\n                return gameSign;\n            }\n        }\n        return null;\n    }\n\n    public static GameSign tryToCreate(DungeonsXL plugin, SignChangeEvent event) {\n        if (!event.getLine(0).equalsIgnoreCase(SIGN_TAG)) {\n            return null;\n        }\n        if (!event.getLine(1).equalsIgnoreCase(GAME_SIGN_TAG)) {\n            return null;\n        }\n\n        String identifier = event.getLine(2);\n        String[] data = event.getLine(3).split(\",\");\n        int maxGroupsPerGame = NumberUtil.parseInt(data[0], 1);\n        int startIfElementsAtLeast = -1;\n        if (data.length > 1) {\n            startIfElementsAtLeast = NumberUtil.parseInt(data[1], -1);\n        }\n\n        return tryToCreate(plugin, event.getBlock(), identifier, maxGroupsPerGame, startIfElementsAtLeast);\n    }\n\n    public static GameSign tryToCreate(DungeonsXL plugin, Block startSign, String identifier, int maxElements, int startIfElementsAtLeast) {\n        onCreation(plugin, startSign, identifier, maxElements, startIfElementsAtLeast);\n        GameSign sign = new GameSign(plugin, plugin.getGlobalProtectionCache().generateId(GameSign.class, startSign.getWorld()), startSign, identifier,\n                maxElements, startIfElementsAtLeast);\n        plugin.getGlobalProtectionCache().addProtection(sign);\n        return sign;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtection.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.util.Collection;\nimport org.bukkit.Bukkit;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class GlobalProtection {\n\n    protected DungeonsXL plugin;\n\n    public static final String SIGN_TAG = \"[DXL]\";\n\n    private String world;\n    private int id;\n\n    protected GlobalProtection(DungeonsXL plugin, World world, int id) {\n        this.plugin = plugin;\n\n        this.world = world.getName();\n        this.id = id;\n    }\n\n    /**\n     * @return the world\n     */\n    public World getWorld() {\n        return Bukkit.getWorld(world);\n    }\n\n    /**\n     * @return the id\n     */\n    public int getId() {\n        return id;\n    }\n\n    /* Actions */\n    /**\n     * Delete this protection.\n     */\n    public void delete() {\n        plugin.getGlobalProtectionCache().removeProtection(this);\n    }\n\n    public boolean onBreak(DGlobalPlayer dPlayer) {\n        if (dPlayer.isInBreakMode()) {\n            delete();\n            MessageUtil.sendMessage(dPlayer.getPlayer(), DMessage.PLAYER_PROTECTED_BLOCK_DELETED.getMessage());\n            MessageUtil.sendMessage(dPlayer.getPlayer(), DMessage.CMD_BREAK_PROTECTED_MODE.getMessage());\n            dPlayer.setInBreakMode(false);\n            return false;\n\n        } else {\n            return true;\n        }\n    }\n\n    /* Abstracts */\n    /**\n     * @return the path in the global data file\n     */\n    public abstract String getDataPath();\n\n    public abstract void save(ConfigurationSection config);\n\n    public UnloadedProtection unload() {\n        String path = getDataPath() + \".\" + getWorld().getName() + \".\" + getId();\n        ConfigurationSection config;\n        if (!plugin.getGlobalProtectionCache().getConfig().contains(path)) {\n            config = plugin.getGlobalProtectionCache().getConfig().createSection(path);\n        } else {\n            config = plugin.getGlobalProtectionCache().getConfig().getConfigurationSection(path);\n        }\n        return UnloadedProtection.create(plugin, getClass(), getWorld().getName(), getId(), config);\n    }\n\n    /**\n     * @return a collection of all blocks covered by this protection\n     */\n    public abstract Collection<Block> getBlocks();\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{ID=\" + id + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtectionCache.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.configuration.file.YamlConfiguration;\n\n/**\n * @author Daniel Saukel\n */\npublic class GlobalProtectionCache {\n\n    private DungeonsXL plugin;\n\n    private File file;\n    private FileConfiguration config;\n\n    private Set<GlobalProtection> protections = new HashSet<>();\n    private Map<UnloadedProtection, String> unloaded = new HashMap<>();\n\n    public GlobalProtectionCache(DungeonsXL plugin) {\n        this.plugin = plugin;\n        file = new File(plugin.getDataFolder(), \"data.yml\");\n        if (!file.exists()) {\n            try {\n                file.createNewFile();\n            } catch (IOException exception) {\n                exception.printStackTrace();\n            }\n        }\n        config = YamlConfiguration.loadConfiguration(file);\n    }\n\n    public FileConfiguration getConfig() {\n        return config;\n    }\n\n    /**\n     * @param location the location to check\n     * @return the protection which covers this location\n     */\n    public GlobalProtection getByLocation(Location location) {\n        return getByBlock(location.getBlock());\n    }\n\n    /**\n     * @param block the block to check\n     * @return the protection which covers this block\n     */\n    public GlobalProtection getByBlock(Block block) {\n        for (GlobalProtection protection : protections) {\n            if (protection.getBlocks().contains(block)) {\n                return protection;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @return the protections\n     */\n    public Set<GlobalProtection> getProtections() {\n        return protections;\n    }\n\n    /**\n     * @return the protections that are known but not loaded yet\n     */\n    public Map<UnloadedProtection, String> getUnloadedProtections() {\n        return unloaded;\n    }\n\n    /**\n     * @param type All protections which are an instance of it will be returned.\n     * @return the protections of the type\n     */\n    public Set<GlobalProtection> getProtections(Class<? extends GlobalProtection> type) {\n        Set<GlobalProtection> protectionsOfType = new HashSet<>();\n        for (GlobalProtection protection : protections) {\n            if (protection.getClass() == type) {\n                protectionsOfType.add(protection);\n            }\n        }\n        return protectionsOfType;\n    }\n\n    /**\n     * @param protection the protection type to add\n     */\n    public void addProtection(GlobalProtection protection) {\n        protections.add(protection);\n    }\n\n    /**\n     * @param protection the protection to remove\n     */\n    public void removeProtection(GlobalProtection protection) {\n        protections.remove(protection);\n    }\n\n    public void loadAll() {\n        ConfigurationSection gameSigns = config.getConfigurationSection(\"protections.gameSigns\");\n        ConfigurationSection groupSigns = config.getConfigurationSection(\"protections.groupSigns\");\n        ConfigurationSection leaveSigns = config.getConfigurationSection(\"protections.leaveSigns\");\n        ConfigurationSection portals = config.getConfigurationSection(\"protections.portals\");\n        if (gameSigns != null) {\n            for (String worldName : gameSigns.getValues(false).keySet()) {\n                ConfigurationSection ws = gameSigns.getConfigurationSection(worldName);\n                if (ws == null) {\n                    continue;\n                }\n                for (Entry<String, Object> entry : ws.getValues(false).entrySet()) {\n                    World world = Bukkit.getWorld(worldName);\n                    if (world != null) {\n                        addProtection(new GameSign(plugin, world, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey())));\n                    } else {\n                        UnloadedProtection protection = UnloadedProtection.create(plugin, GameSign.class, worldName, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey()));\n                        unloaded.put(protection, worldName);\n                    }\n                }\n            }\n        }\n\n        if (groupSigns != null) {\n            for (String worldName : groupSigns.getValues(false).keySet()) {\n                ConfigurationSection ws = groupSigns.getConfigurationSection(worldName);\n                if (ws == null) {\n                    continue;\n                }\n                for (Entry<String, Object> entry : ws.getValues(false).entrySet()) {\n                    World world = Bukkit.getWorld(worldName);\n                    if (world != null) {\n                        addProtection(new GroupSign(plugin, world, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey())));\n                    } else {\n                        UnloadedProtection protection = UnloadedProtection.create(plugin, GroupSign.class, worldName, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey()));\n                        unloaded.put(protection, worldName);\n                    }\n                }\n            }\n        }\n\n        if (leaveSigns != null) {\n            for (String worldName : leaveSigns.getValues(false).keySet()) {\n                ConfigurationSection ws = leaveSigns.getConfigurationSection(worldName);\n                if (ws == null) {\n                    continue;\n                }\n                for (Entry<String, Object> entry : ws.getValues(false).entrySet()) {\n                    World world = Bukkit.getWorld(worldName);\n                    if (world != null) {\n                        addProtection(new LeaveSign(plugin, world, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey())));\n                    } else {\n                        UnloadedProtection protection = UnloadedProtection.create(plugin, LeaveSign.class, worldName, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey()));\n                        unloaded.put(protection, worldName);\n                    }\n                }\n            }\n        }\n\n        if (portals != null) {\n            for (String worldName : portals.getValues(false).keySet()) {\n                ConfigurationSection ws = portals.getConfigurationSection(worldName);\n                if (ws == null) {\n                    continue;\n                }\n                for (Entry<String, Object> entry : ws.getValues(false).entrySet()) {\n                    World world = Bukkit.getWorld(worldName);\n                    if (world != null) {\n                        addProtection(new DPortal(plugin, world, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey())));\n                    } else {\n                        UnloadedProtection protection = UnloadedProtection.create(plugin, DPortal.class, worldName, NumberUtil.parseInt(entry.getKey()), ws.getConfigurationSection(entry.getKey()));\n                        unloaded.put(protection, worldName);\n                    }\n                }\n            }\n        }\n    }\n\n    public void saveAll() {\n        if (!file.exists()) {\n            try {\n                file.createNewFile();\n            } catch (IOException exception) {\n                exception.printStackTrace();\n            }\n        }\n        for (GlobalProtection protection : protections) {\n            String path = protection.getDataPath() + \".\" + protection.getWorld().getName() + \".\" + protection.getId();\n            if (!config.contains(path)) {\n                protection.save(config.createSection(path));\n            }\n        }\n        try {\n            config.save(file);\n        } catch (IOException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n    /**\n     * @param type  Each type is stored seperately.\n     * @param world Each world has its own IDs.\n     * @return an unused ID number for a new protection\n     */\n    public int generateId(Class<? extends GlobalProtection> type, World world) {\n        int id = 1;\n        for (GlobalProtection protection : protections) {\n            if (protection.getClass() == type && id <= protection.getId()) {\n                id = protection.getId() + 1;\n            }\n        }\n        return id;\n    }\n\n    /**\n     * @param block the block to check\n     * @return if the block is protected by a GlobalProtection\n     */\n    public boolean isProtectedBlock(Block block) {\n        for (GlobalProtection protection : protections) {\n            if (protection.getBlocks().contains(block)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    public void updateGroupSigns(DGroup dGroupSearch) {\n        for (GlobalProtection protection : getProtections(GroupSign.class)) {\n            GroupSign groupSign = (GroupSign) protection;\n            if (dGroupSearch != null && groupSign.getGroup() == dGroupSearch) {\n                if (dGroupSearch.isEmpty()) {\n                    groupSign.setGroup(null);\n                }\n                groupSign.update();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtectionListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.event.block.BlockPhysicsEvent;\nimport org.bukkit.event.block.BlockPlaceEvent;\nimport org.bukkit.event.block.BlockSpreadEvent;\nimport org.bukkit.event.block.SignChangeEvent;\nimport org.bukkit.event.entity.EntityExplodeEvent;\nimport org.bukkit.event.player.PlayerBucketFillEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.event.player.PlayerMoveEvent;\nimport org.bukkit.event.player.PlayerPortalEvent;\nimport org.bukkit.event.world.WorldLoadEvent;\nimport org.bukkit.event.world.WorldUnloadEvent;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel, Wooyoung Son, Frank Baumann, Milan Albrecht\n */\npublic class GlobalProtectionListener implements Listener {\n\n    private DungeonsXL plugin;\n\n    public GlobalProtectionListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockBreakWithSignOnIt(BlockBreakEvent event) {\n        Block block = event.getBlock();\n        Player player = event.getPlayer();\n\n        Block blockAbove = block.getRelative(BlockFace.UP);\n        //get the above block and return if there is nothing\n        if (blockAbove == null) {\n            return;\n        }\n\n        //return if above block is not a sign\n        if (!Category.SIGNS.containsBlock(blockAbove)) {\n            return;\n        }\n\n        //let onBreak() method to handle the sign\n        BlockBreakEvent bbe = new BlockBreakEvent(blockAbove, player);\n        onBlockBreak(bbe);\n\n        //follow the onBreak()\n        event.setCancelled(bbe.isCancelled());\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockBreak(BlockBreakEvent event) {\n        Block block = event.getBlock();\n        Player player = event.getPlayer();\n        DGlobalPlayer dGlobalPlayer = (DGlobalPlayer) plugin.getPlayerCache().get(player);\n\n        GlobalProtection protection = plugin.getGlobalProtectionCache().getByBlock(block);\n        if (protection != null) {\n            if (protection.onBreak(dGlobalPlayer)) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockPlace(BlockPlaceEvent event) {\n        if (DPortal.getByBlock(plugin, event.getBlock()) != null) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onPlayerBucketFill(PlayerBucketFillEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        Block block = event.getBlockClicked();\n        if (DPortal.getByBlock(plugin, block) != null) {\n            event.setCancelled(true);\n            // Workaround for a bug of Bukkit\n            event.getPlayer().sendBlockChange(block.getLocation(), block.getType(), (byte) 0);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockSpread(BlockSpreadEvent event) {\n        if (DPortal.getByBlock(plugin, event.getBlock()) != null) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockPhysics(BlockPhysicsEvent event) {\n        if (DPortal.getByBlock(plugin, event.getBlock()) != null) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onEntityExplode(EntityExplodeEvent event) {\n        List<Block> blocklist = event.blockList();\n        for (Block block : blocklist) {\n            if (plugin.getGlobalProtectionCache().isProtectedBlock(block)) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerMove(PlayerMoveEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player) || plugin.isInstance(event.getTo().getWorld())) {\n            return;\n        }\n\n        if (!plugin.getMainConfig().isStrictMovementCheckEnabled()) {\n            Block blockFrom = event.getFrom().getBlock();\n            Block blockTo = event.getTo().getBlock();\n            if (blockFrom.equals(blockTo)) {\n                return;\n            }\n        }\n\n        DPortal dPortal = DPortal.getByLocation(plugin, player.getEyeLocation());\n        if (dPortal == null) {\n            return;\n        }\n\n        dPortal.teleport(player);\n    }\n\n    @EventHandler\n    public void onPlayerPortal(PlayerPortalEvent event) {\n        Block block1 = event.getFrom().getBlock();\n        Block block2 = block1.getRelative(BlockFace.UP);\n        Block block3 = block2.getRelative(BlockFace.UP);\n        Block block4 = block1.getRelative(BlockFace.DOWN);\n        if (isPortalInNearBy(block1) || isPortalInNearBy(block2) || isPortalInNearBy(block3) || isPortalInNearBy(block4)) {\n            event.setCancelled(true);\n        }\n    }\n\n    private boolean isPortalInNearBy(Block block1) {\n        Block block2 = block1.getRelative(BlockFace.WEST);\n        Block block3 = block1.getRelative(BlockFace.NORTH);\n        Block block4 = block1.getRelative(BlockFace.EAST);\n        Block block5 = block1.getRelative(BlockFace.SOUTH);\n        Block block6 = block2.getRelative(BlockFace.NORTH);\n        Block block7 = block2.getRelative(BlockFace.SOUTH);\n        Block block8 = block4.getRelative(BlockFace.NORTH);\n        Block block9 = block4.getRelative(BlockFace.SOUTH);\n        return (DPortal.getByBlock(plugin, block1) != null || DPortal.getByBlock(plugin, block2) != null || DPortal.getByBlock(plugin, block3) != null\n                || DPortal.getByBlock(plugin, block4) != null || DPortal.getByBlock(plugin, block5) != null || DPortal.getByBlock(plugin, block6) != null\n                || DPortal.getByBlock(plugin, block7) != null || DPortal.getByBlock(plugin, block8) != null || DPortal.getByBlock(plugin, block9) != null);\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onPortalCreation(PlayerInteractEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        DGlobalPlayer dPlayer = (DGlobalPlayer) plugin.getPlayerCache().get(player);\n        if (!dPlayer.isCreatingPortal()) {\n            return;\n        }\n        ItemStack item = event.getItem();\n        Block block = event.getClickedBlock();\n        if (item == null || !VanillaItem.WOODEN_SWORD.is(item) || block == null) {\n            return;\n        }\n\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(DPortal.class)) {\n            DPortal dPortal = (DPortal) protection;\n            if (dPortal.isActive() || dPortal != dPlayer.getPortal()) {\n                continue;\n            }\n\n            if (dPortal.getBlock1() == null) {\n                dPortal.setBlock1(event.getClickedBlock());\n                dPlayer.sendMessage(DMessage.PLAYER_PORTAL_PROGRESS.getMessage());\n\n            } else if (dPortal.getBlock2() == null) {\n                dPortal.setBlock2(event.getClickedBlock());\n                dPortal.setActive(true);\n                dPortal.create(dPlayer);\n            }\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onInteract(PlayerInteractEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player) || plugin.getPlayerCache().get(player).isInBreakMode()) {\n            return;\n        }\n        Block clickedBlock = event.getClickedBlock();\n        if (clickedBlock == null) {\n            return;\n        }\n\n        if (Category.SIGNS.containsBlock(clickedBlock)) {\n            GroupSign groupSign = GroupSign.getByBlock(plugin, clickedBlock);\n            if (groupSign != null) {\n                groupSign.onPlayerInteract(clickedBlock, player);\n                event.setCancelled(true);\n            }\n\n            GameSign gameSign = GameSign.getByBlock(plugin, clickedBlock);\n            if (gameSign != null) {\n                gameSign.onPlayerInteract(clickedBlock, player);\n                event.setCancelled(true);\n            }\n\n            LeaveSign leaveSign = LeaveSign.getByBlock(plugin, clickedBlock);\n            if (leaveSign != null) {\n                leaveSign.onPlayerInteract(player);\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onSignChange(SignChangeEvent event) {\n        Player player = event.getPlayer();\n        Block block = event.getBlock();\n        BlockState state = block.getState();\n        if (!(state instanceof Sign)) {\n            return;\n        }\n\n        String[] lines = event.getLines();\n\n        // Group Signs\n        if (plugin.getEditWorld(player.getWorld()) == null) {\n            if (!DPermission.hasPermission(player, DPermission.SIGN)) {\n                return;\n            }\n\n            if (!lines[0].equalsIgnoreCase(GlobalProtection.SIGN_TAG)) {\n                return;\n            }\n\n            if (lines[1].equalsIgnoreCase(GroupSign.GROUP_SIGN_TAG)) {\n                if (GroupSign.tryToCreate(plugin, event) != null) {\n                    event.setCancelled(true);\n                }\n\n            } else if (lines[1].equalsIgnoreCase(GameSign.GAME_SIGN_TAG)) {\n                if (GameSign.tryToCreate(plugin, event) != null) {\n                    event.setCancelled(true);\n                }\n\n            } else if (lines[1].equalsIgnoreCase(LeaveSign.LEAVE_SIGN_TAG)) {\n                Sign sign = (Sign) state;\n                plugin.getGlobalProtectionCache().addProtection(new LeaveSign(plugin, plugin.getGlobalProtectionCache().generateId(LeaveSign.class, sign.getWorld()), sign));\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler\n    public void onWorldLoad(WorldLoadEvent event) {\n        World world = event.getWorld();\n        MessageUtil.debug(plugin, \"New world detected.\");\n        Set<Entry<UnloadedProtection, String>> protections = plugin.getGlobalProtectionCache().getUnloadedProtections().entrySet();\n        for (Entry<UnloadedProtection, String> entry : protections.toArray(new Entry[protections.size()])) {\n            MessageUtil.debug(plugin, \"Checking unloaded protection \" + entry);\n            if (world.getName().equals(entry.getValue())) {\n                MessageUtil.debug(plugin, \"New world has global DXL data: \" + entry);\n                plugin.getGlobalProtectionCache().addProtection(entry.getKey().load(world));\n            }\n        }\n    }\n\n    @EventHandler\n    public void onWorldUnload(WorldUnloadEvent event) {\n        World world = event.getWorld();\n        if (world.getName().startsWith(\"DXL_\")) {\n            return;\n        }\n        MessageUtil.debug(plugin, \"Unloaded world detected.\");\n        Collection<GlobalProtection> protections = plugin.getGlobalProtectionCache().getProtections();\n        for (GlobalProtection protection : protections.toArray(new GlobalProtection[protections.size()])) {\n            if (protection.getWorld().getName().equals(world.getName())) {\n                protection.delete();\n                plugin.getGlobalProtectionCache().getUnloadedProtections().put(protection.unload(), world.getName());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/GroupSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent.Cause;\nimport de.erethon.dungeonsxl.api.player.GroupAdapter;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.SignChangeEvent;\n\n/**\n * A sign to form a group and to define its dungeon.\n *\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class GroupSign extends JoinSign {\n\n    public static final String GROUP_SIGN_TAG = \"Group\";\n\n    private String groupName;\n    private DGroup group;\n\n    public GroupSign(DungeonsXL plugin, int id, Block startSign, String identifier, int maxPlayersPerGroup, int startIfElementsAtLeast, String groupName) {\n        super(plugin, id, startSign, identifier, maxPlayersPerGroup, startIfElementsAtLeast);\n        this.groupName = groupName;\n    }\n\n    public GroupSign(DungeonsXL plugin, World world, int id, ConfigurationSection config) {\n        super(plugin, world, id, config);\n        groupName = config.getString(\"groupName\");\n    }\n\n    /**\n     * @return the attached group\n     */\n    public DGroup getGroup() {\n        return group;\n    }\n\n    /**\n     * @param group the group to set\n     */\n    public void setGroup(DGroup group) {\n        this.group = group;\n    }\n\n    /**\n     * Update this group sign to show the group(s) correctly.\n     */\n    @Override\n    public void update() {\n        if (!(startSign.getState() instanceof Sign)) {\n            return;\n        }\n\n        super.update();\n        Sign sign = (Sign) startSign.getState();\n\n        if (group == null) {\n            loadedWorld = false;\n            sign.setLine(0, DMessage.SIGN_GLOBAL_NEW_GROUP.getMessage());\n            sign.update();\n            return;\n        }\n\n        if (group.getMembers().size() >= startIfElementsAtLeast && startIfElementsAtLeast != -1 && !loadedWorld) {\n            loadedWorld = true;\n            group.teleport();\n        }\n\n        if (group.isPlaying()) {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_IS_PLAYING.getMessage());\n\n        } else if (group.getMembers().size() >= maxElements) {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_FULL.getMessage());\n\n        } else {\n            sign.setLine(0, DMessage.SIGN_GLOBAL_JOIN_GROUP.getMessage());\n        }\n\n        int j = 1;\n        Sign rowSign = sign;\n\n        for (Player player : group.getMembers().getOnlinePlayers()) {\n            if (j > 3) {\n                j = 0;\n                rowSign = (Sign) sign.getBlock().getRelative(0, -1, 0).getState();\n            }\n\n            if (rowSign != null) {\n                rowSign.setLine(j, player.getName());\n            }\n\n            j++;\n            rowSign.update();\n        }\n\n        sign.update();\n    }\n\n    @Override\n    public String getDataPath() {\n        return \"protections.groupSigns\";\n    }\n\n    @Override\n    public void save(ConfigurationSection config) {\n        super.save(config);\n        config.set(\"groupName\", groupName);\n    }\n\n    public void onPlayerInteract(Block block, Player player) {\n        DGroup playerGroup = (DGroup) plugin.getPlayerGroup(player);\n        if (playerGroup != null) {\n            if (playerGroup.getLeader().equals(player)) {\n                if (playerGroup.getGroupSign() != null || group != null || maxElements < playerGroup.getMembers().size()) {\n                    MessageUtil.sendMessage(player, DMessage.ERROR_LEAVE_GROUP.getMessage());\n                    return;\n                } else {\n                    group = playerGroup;\n                    group.setGroupSign(this);\n                    group.setDungeon(dungeon);\n                    update();\n                    return;\n                }\n            } else {\n                playerGroup.removeMember(player, true);\n            }\n        }\n\n        Block topBlock = block.getRelative(0, startSign.getY() - block.getY(), 0);\n        if (!(topBlock.getState() instanceof Sign)) {\n            return;\n        }\n\n        Sign topSign = (Sign) topBlock.getState();\n\n        if (topSign.getLine(0).equals(DMessage.SIGN_GLOBAL_NEW_GROUP.getMessage())) {\n            if (dungeon == null) {\n                MessageUtil.sendMessage(player, DMessage.ERROR_SIGN_WRONG_FORMAT.getMessage());\n                return;\n            }\n\n            for (GroupAdapter adapter : plugin.getGroupAdapters()) {\n                if (adapter.isExternalGroupMember(player)) {\n                    group = (DGroup) adapter.getOrCreateDungeonGroup(adapter.getExternalGroup(player), maxElements);\n                }\n            }\n            if (group == null) {\n                group = DGroup.create(plugin, Cause.GROUP_SIGN, player, groupName, null, dungeon);\n            }\n            if (group != null) {\n                group.setDungeon(dungeon);\n                group.setGroupSign(this);\n                update();\n            }\n\n        } else if (topSign.getLine(0).equals(DMessage.SIGN_GLOBAL_JOIN_GROUP.getMessage())) {\n            group.addMember(player);\n            update();\n        }\n    }\n\n    /* Statics */\n    /**\n     * @param plugin the plugin instance\n     * @param block  a block which is protected by the returned GroupSign\n     * @return the group sign the block belongs to, null if it belongs to none\n     */\n    public static GroupSign getByBlock(DungeonsXL plugin, Block block) {\n        if (!Category.SIGNS.containsBlock(block)) {\n            return null;\n        }\n\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(GroupSign.class)) {\n            GroupSign groupSign = (GroupSign) protection;\n            Block start = groupSign.startSign;\n            if (start == block || (start.getX() == block.getX() && start.getZ() == block.getZ() && (start.getY() >= block.getY() && start.getY() - groupSign.verticalSigns <= block.getY()))) {\n                return groupSign;\n            }\n        }\n\n        return null;\n    }\n\n    public static GroupSign tryToCreate(DungeonsXL plugin, SignChangeEvent event) {\n        if (!event.getLine(0).equalsIgnoreCase(SIGN_TAG)) {\n            return null;\n        }\n        if (!event.getLine(1).equalsIgnoreCase(GROUP_SIGN_TAG)) {\n            return null;\n        }\n\n        String identifier = event.getLine(2);\n\n        String[] data = event.getLine(3).split(\",\");\n        int maxPlayersPerGroup = 1;\n        String groupName = null;\n        int startIfElementsAtLeast = -1;\n        if (data.length >= 1) {\n            maxPlayersPerGroup = NumberUtil.parseInt(data[0], 1);\n        }\n        if (data.length >= 2) {\n            groupName = data[1];\n        }\n        if (data.length == 3) {\n            startIfElementsAtLeast = NumberUtil.parseInt(data[2], -1);\n        }\n\n        return tryToCreate(plugin, event.getBlock(), identifier, maxPlayersPerGroup, startIfElementsAtLeast, groupName);\n    }\n\n    public static GroupSign tryToCreate(DungeonsXL plugin, Block startSign, String identifier, int maxElements, int startIfElementsAtLeast, String groupName) {\n        onCreation(plugin, startSign, identifier, maxElements, startIfElementsAtLeast);\n        GroupSign sign = new GroupSign(plugin, plugin.getGlobalProtectionCache().generateId(GroupSign.class, startSign.getWorld()), startSign, identifier,\n                maxElements, startIfElementsAtLeast, groupName);\n        plugin.getGlobalProtectionCache().addProtection(sign);\n        return sign;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/JoinSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.util.LWCUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.Sign;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class JoinSign extends GlobalProtection {\n\n    protected Dungeon dungeon;\n    protected int maxElements;\n    protected int startIfElementsAtLeast = -1;\n    protected Block startSign;\n    protected int verticalSigns;\n    protected Set<Block> blocks;\n    protected boolean loadedWorld;\n\n    protected JoinSign(DungeonsXL plugin, int id, Block startSign, String identifier, int maxElements, int startIfElementsAtLeast) {\n        super(plugin, startSign.getWorld(), id);\n\n        this.startSign = startSign;\n        dungeon = plugin.getDungeonRegistry().get(identifier);\n        verticalSigns = (int) Math.ceil((float) (1 + maxElements) / 4);\n\n        this.maxElements = maxElements;\n        if (startIfElementsAtLeast > 0 && startIfElementsAtLeast <= maxElements) {\n            this.startIfElementsAtLeast = startIfElementsAtLeast;\n        }\n\n        // 1.20 doesn't like setting sign text in the same tick as the creation event\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                update();\n            }\n        }.runTaskLater(plugin, 1L);\n    }\n\n    protected JoinSign(DungeonsXL plugin, World world, int id, ConfigurationSection config) {\n        super(plugin, world, id);\n\n        startSign = world.getBlockAt(config.getInt(\"x\"), config.getInt(\"y\"), config.getInt(\"z\"));\n        String identifier = config.getString(\"dungeon\");\n        dungeon = plugin.getDungeonRegistry().get(identifier);\n\n        // LEGACY\n        if (config.contains(\"maxElements\")) {\n            maxElements = config.getInt(\"maxElements\");\n        } else if (config.contains(\"maxGroupsPerGame\")) {\n            maxElements = config.getInt(\"maxGroupsPerGame\");\n        } else if (config.contains(\"maxPlayersPerGroup\")) {\n            maxElements = config.getInt(\"maxPlayersPerGroup\");\n        }\n        startIfElementsAtLeast = config.getInt(\"startIfElementsAtLeast\", -1);\n\n        verticalSigns = (int) Math.ceil((float) (1 + maxElements) / 4);\n\n        update();\n    }\n\n    /**\n     * @return the dungeon\n     */\n    public Dungeon getDungeon() {\n        return dungeon;\n    }\n\n    /**\n     * @param dungeon the dungeon to set\n     */\n    public void setDungeon(Dungeon dungeon) {\n        this.dungeon = dungeon;\n    }\n\n    /**\n     * @return the maximum element count per sign\n     */\n    public int getMaxElements() {\n        return maxElements;\n    }\n\n    /**\n     * @param amount the maximum element count per sign\n     */\n    public void setMaxElements(int amount) {\n        maxElements = amount;\n    }\n\n    /**\n     * Returns the minimum amount of elements required to start the dungeon countdown\n     *\n     * @return the minimum amount of elements required to start the dungeon countdown;<br>\n     * -1 if the dungeon is not instantiated only through the sign.\n     */\n    public int getStartIfElementsAtLeastAmount() {\n        return startIfElementsAtLeast;\n    }\n\n    /**\n     * Sets the minimum amount of elements required to start the dungeon countdown\n     *\n     * @param amount the amount to set\n     */\n    public void setStartIfElementsAtLeastAmount(int amount) {\n        if ((amount > 0 || amount == -1) && amount <= maxElements) {\n            startIfElementsAtLeast = amount;\n        } else {\n            throw new IllegalArgumentException(\"startIfElementsAtLeastAmount is < 0 or < maxElements\");\n        }\n    }\n\n    @Override\n    public Set<Block> getBlocks() {\n        if (blocks == null) {\n            blocks = new HashSet<>();\n\n            HashSet<Block> toAdd = new HashSet<>();\n            int y = -1 * verticalSigns;\n            while (y != 1) {\n                blocks.add(startSign.getRelative(0, y, 0));\n                y++;\n            }\n\n            for (Block block : blocks) {\n                int i = verticalSigns;\n                do {\n                    i--;\n\n                    Block beneath = block.getLocation().add(0, -1 * i, 0).getBlock();\n                    toAdd.add(beneath);\n                    toAdd.add(BlockUtilCompat.getAttachedBlock(beneath));\n\n                } while (i >= 0);\n            }\n            blocks.addAll(toAdd);\n        }\n\n        return blocks;\n    }\n\n    /**\n     * Clears signs\n     */\n    public void update() {\n        int y = -1 * verticalSigns;\n        while (startSign.getRelative(0, y + 1, 0).getState() instanceof Sign && y != 0) {\n            Sign subsign = (Sign) startSign.getRelative(0, y + 1, 0).getState();\n            subsign.setLine(0, \"\");\n            subsign.setLine(1, \"\");\n            subsign.setLine(2, \"\");\n            subsign.setLine(3, \"\");\n            subsign.update();\n            y++;\n        }\n    }\n\n    @Override\n    public void save(ConfigurationSection config) {\n        config.set(\"x\", startSign.getX());\n        config.set(\"y\", startSign.getY());\n        config.set(\"z\", startSign.getZ());\n        if (dungeon != null) {\n            config.set(\"dungeon\", dungeon.getName());\n        }\n        config.set(\"maxElements\", maxElements);\n        if (startIfElementsAtLeast != -1) {\n            config.set(\"startIfElementsAtLeast\", startIfElementsAtLeast);\n        }\n    }\n\n    protected static void onCreation(DungeonsXL plugin, Block startSign, String identifier, int maxElements, int startIfElementsAtLeast) {\n        World world = startSign.getWorld();\n        BlockFace facing = DungeonsXL.BLOCK_ADAPTER.getFacing(startSign);\n        int x = startSign.getX(), y = startSign.getY(), z = startSign.getZ();\n\n        int verticalSigns = (int) Math.ceil((float) (1 + maxElements) / 4);\n        while (verticalSigns > 1) {\n            Block block = world.getBlockAt(x, y - verticalSigns + 1, z);\n            block.setType(startSign.getType(), false);\n            DungeonsXL.BLOCK_ADAPTER.setFacing(block, facing);\n            verticalSigns--;\n        }\n\n        LWCUtil.removeProtection(startSign);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/LeaveSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.util.LWCUtil;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.ChatColor;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.block.Sign;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * A sign to leave a group.\n *\n * @author Frank Baumann\n */\npublic class LeaveSign extends GlobalProtection {\n\n    public static final String LEAVE_SIGN_TAG = \"Leave\";\n\n    private Block sign;\n    private Set<Block> blocks;\n\n    public LeaveSign(DungeonsXL plugin, int id, Sign sign) {\n        super(plugin, sign.getWorld(), id);\n\n        this.sign = sign.getBlock();\n        // 1.20 doesn't like setting sign text in the same tick as the creation event\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                setText();\n            }\n        }.runTaskLater(plugin, 1L);\n\n        LWCUtil.removeProtection(sign.getBlock());\n    }\n\n    public LeaveSign(DungeonsXL plugin, World world, int id, ConfigurationSection config) {\n        super(plugin, world, id);\n\n        sign = world.getBlockAt(config.getInt(\"x\"), config.getInt(\"y\"), config.getInt(\"z\"));\n        setText();\n\n        LWCUtil.removeProtection(sign);\n    }\n\n    /* Getters and setters */\n    @Override\n    public Set<Block> getBlocks() {\n        if (blocks == null) {\n            blocks = new HashSet<>();\n\n            blocks.add(sign);\n            blocks.add(BlockUtilCompat.getAttachedBlock(sign));\n        }\n\n        return blocks;\n    }\n\n    /* Actions */\n    public void setText() {\n        BlockState state = sign.getState();\n        if (state instanceof Sign) {\n            Sign sign = (Sign) state;\n            sign.setLine(0, ChatColor.BLUE + \"############\");\n            sign.setLine(1, DMessage.SIGN_LEAVE.getMessage());\n            sign.setLine(2, \"\");\n            sign.setLine(3, ChatColor.BLUE + \"############\");\n            sign.update();\n        }\n    }\n\n    public void onPlayerInteract(Player player) {\n        GamePlayer gamePlayer = plugin.getPlayerCache().getGamePlayer(player);\n\n        if (gamePlayer != null) {\n            gamePlayer.leave();\n\n        } else {\n            PlayerGroup group = plugin.getPlayerGroup(player);\n            if (group != null) {\n                group.removeMember(player);\n                MessageUtil.sendMessage(player, DMessage.PLAYER_LEAVE_GROUP.getMessage());\n            }\n        }\n    }\n\n    @Override\n    public String getDataPath() {\n        return \"protections.leaveSigns\";\n    }\n\n    @Override\n    public void save(ConfigurationSection config) {\n        config.set(\"x\", sign.getX());\n        config.set(\"y\", sign.getY());\n        config.set(\"z\", sign.getZ());\n    }\n\n    /* Statics */\n    /**\n     * @param plugin the plugin instance\n     * @param block  a block which is protected by the returned LeaveSign\n     * @return the leave sign the block belongs to, null if it belongs to none\n     */\n    public static LeaveSign getByBlock(DungeonsXL plugin, Block block) {\n        for (GlobalProtection protection : plugin.getGlobalProtectionCache().getProtections(LeaveSign.class)) {\n            LeaveSign leaveSign = (LeaveSign) protection;\n\n            if (leaveSign.getBlocks().contains(block)) {\n                return leaveSign;\n            }\n        }\n\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/global/UnloadedProtection.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.global;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport org.bukkit.World;\nimport org.bukkit.configuration.ConfigurationSection;\n\n/**\n * @author Daniel Saukel\n */\npublic class UnloadedProtection<T extends GlobalProtection> {\n\n    private DungeonsXL plugin;\n    private GlobalProtectionCache cache;\n\n    private Constructor constructor;\n    private String worldName;\n    private int id;\n    private ConfigurationSection config;\n\n    private UnloadedProtection() {}\n\n    public static <T extends GlobalProtection> UnloadedProtection create(DungeonsXL plugin, Class<T> type, String worldName, int id, ConfigurationSection config) {\n        UnloadedProtection protection = new UnloadedProtection();\n        protection.plugin = plugin;\n        try {\n            protection.constructor = type.getConstructor(DungeonsXL.class, World.class, int.class, ConfigurationSection.class);\n        } catch (NoSuchMethodException | SecurityException exception) {\n            return null;\n        }\n        protection.worldName = worldName;\n        protection.id = id;\n        protection.config = config;\n\n        protection.cache = plugin.getGlobalProtectionCache();\n        return protection;\n    }\n\n    public T load(World world) {\n        if (!world.getName().equals(worldName)) {\n            throw new IllegalArgumentException(\"World mismatch: Expected \" + worldName + \", but received \" + world);\n        }\n\n        T protection = null;\n        try {\n            protection = (T) constructor.newInstance(plugin, world, id, config);\n        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {\n            MessageUtil.log(plugin, \"Could not find or invoke \" + constructor);\n            exception.printStackTrace();\n        }\n        if (protection != null) {\n            cache.getUnloadedProtections().remove(this);\n        }\n        return protection;\n    }\n\n    @Override\n    public String toString() {\n        return \"UnloadedProtection{type=\" + constructor.getDeclaringClass().getName() + \"; \" + \"world=\" + worldName + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/CitizensMobProvider.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.event.NPCDeathEvent;\nimport net.citizensnpcs.api.npc.AbstractNPC;\nimport net.citizensnpcs.api.npc.NPC;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\n\n/**\n * ExternalMobProvider implementation for Citizens.\n *\n * @author Daniel Saukel\n */\npublic class CitizensMobProvider implements ExternalMobProvider, Listener {\n\n    private DungeonsAPI api;\n\n    private static final String IDENTIFIER = \"CI\";\n\n    private DNPCRegistry registry = new DNPCRegistry();\n    private Set<NPC> spawnedNPCs = new HashSet<>();\n\n    public CitizensMobProvider(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    /**\n     * @return the DungeonsXL NPC registry\n     */\n    public DNPCRegistry getNPCRegistry() {\n        return registry;\n    }\n\n    /**\n     * @return the spawned Citizens NPCs\n     */\n    public Set<NPC> getSpawnedNPCs() {\n        return spawnedNPCs;\n    }\n\n    /**\n     * @param npc the NPC to add\n     */\n    public void addSpawnedNPC(NPC npc) {\n        spawnedNPCs.add(npc);\n    }\n\n    /**\n     * @param npc the NPC to remove\n     */\n    public void removeSpawnedNPC(NPC npc) {\n        spawnedNPCs.remove(npc);\n        npc.destroy();\n    }\n\n    public void removeSpawnedNPCs(World world) {\n        Set<NPC> worldNPCs = new HashSet<>();\n        for (NPC npc : spawnedNPCs) {\n            if (npc.getStoredLocation().getWorld().equals(world)) {\n                worldNPCs.add(npc);\n            }\n        }\n        worldNPCs.forEach(this::removeSpawnedNPC);\n    }\n\n    @Override\n    public String getIdentifier() {\n        return IDENTIFIER;\n    }\n\n    @Override\n    public String getRawCommand() {\n        return null;\n    }\n\n    @Override\n    public String getCommand(String mob, String world, double x, double y, double z) {\n        return null;\n    }\n\n    @Override\n    public void summon(String mob, Location location) {\n        NPC source = CitizensAPI.getNPCRegistry().getById(NumberUtil.parseInt(mob));\n        if (!(source instanceof AbstractNPC)) {\n            return;\n        }\n\n        GameWorld gameWorld = api.getGameWorld(location.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        boolean nativeRegistry = gameWorld.getDungeon().getRules().getState(GameRule.USE_NATIVE_CITIZENS_REGISTRY);\n        NPC npc = nativeRegistry ? source.clone() : registry.createTransientClone((AbstractNPC) source);\n        if (npc.isSpawned()) {\n            npc.despawn();\n        }\n\n        npc.spawn(location);\n        spawnedNPCs.add(npc);\n        api.wrapEntity((LivingEntity) npc.getEntity(), gameWorld, mob);\n    }\n\n    /* Listeners */\n    @EventHandler\n    public void onNPCDeath(NPCDeathEvent event) {\n        NPC npc = event.getNPC();\n        if (spawnedNPCs.contains(npc)) {\n            CitizensAPI.getNPCRegistry().deregister(npc);\n            removeSpawnedNPC(npc);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/CustomExternalMobProvider.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\nimport java.util.Map.Entry;\n\n/**\n * A custom external mob provider like defined in the main config file.\n *\n * @author Daniel Saukel\n */\npublic class CustomExternalMobProvider implements ExternalMobProvider {\n\n    private String identifier;\n    private String command;\n\n    public CustomExternalMobProvider(String identifier, String command) {\n        this.identifier = identifier;\n\n        if (command.startsWith(\"/\")) {\n            command = command.replaceFirst(\"/\", \"\");\n        }\n        this.command = command;\n    }\n\n    public CustomExternalMobProvider(Entry<String, Object> entry) {\n        this(entry.getKey(), (String) entry.getValue());\n    }\n\n    @Override\n    public String getIdentifier() {\n        return identifier;\n    }\n\n    @Override\n    public String getRawCommand() {\n        return command;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/DMob.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.event.mob.DungeonMobDeathEvent;\nimport de.erethon.dungeonsxl.api.event.mob.DungeonMobSpawnEvent;\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.trigger.MobTrigger;\nimport de.erethon.dungeonsxl.trigger.WaveTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.mob.ExMob;\nimport de.erethon.xlib.mob.VanillaMob;\nimport java.util.Collection;\nimport java.util.Set;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.event.entity.EntityDeathEvent;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class DMob implements DungeonMob {\n\n    private LivingEntity entity;\n    private ExMob type;\n\n    private String trigger;\n\n    public DMob(LivingEntity entity, GameWorld gameWorld, ExMob type, String trigger) {\n        this.entity = entity;\n        this.type = type != null ? type : VanillaMob.get(entity.getType());\n\n        if (this.type.getSpecies().isAlive() && this.type != VanillaMob.ARMOR_STAND && this.type != VanillaMob.PLAYER\n                && !getDrops(gameWorld.getDungeon().getRules().getState(GameRule.MOB_ITEM_DROPS))) {\n            entity.getEquipment().setHelmetDropChance(0);\n            entity.getEquipment().setChestplateDropChance(0);\n            entity.getEquipment().setLeggingsDropChance(0);\n            entity.getEquipment().setBootsDropChance(0);\n            if (Version.isAtLeast(Version.MC1_9)) {\n                entity.getEquipment().setItemInMainHandDropChance(0);\n                entity.getEquipment().setItemInOffHandDropChance(0);\n            } else {\n                entity.getEquipment().setItemInHandDropChance(0);\n            }\n        }\n\n        DungeonMobSpawnEvent event = new DungeonMobSpawnEvent(this);\n        Bukkit.getPluginManager().callEvent(event);\n        this.trigger = trigger;\n        gameWorld.addMob(this);\n    }\n\n    /* Getters */\n    @Override\n    public LivingEntity getEntity() {\n        return entity;\n    }\n\n    @Override\n    public ExMob getType() {\n        return type;\n    }\n\n    @Override\n    public String getTriggerId() {\n        return trigger;\n    }\n\n    /* Actions */\n    public void onDeath(DungeonsXL plugin, EntityDeathEvent event) {\n        LivingEntity victim = event.getEntity();\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(victim.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        DungeonMobDeathEvent dMobDeathEvent = new DungeonMobDeathEvent(this);\n        Bukkit.getServer().getPluginManager().callEvent(dMobDeathEvent);\n        if (dMobDeathEvent.isCancelled()) {\n            return;\n        }\n\n        if (!getDrops(gameWorld.getDungeon().getRules().getState(GameRule.MOB_ITEM_DROPS))) {\n            event.getDrops().clear();\n        }\n        if (!getDrops(gameWorld.getDungeon().getRules().getState(GameRule.MOB_EXP_DROPS))) {\n            event.setDroppedExp(0);\n        }\n\n        MobTrigger mobTrigger = MobTrigger.getByName(trigger, gameWorld);\n        if (mobTrigger != null) {\n            mobTrigger.trigger(true, null);\n        }\n\n        Collection<Trigger> waveTriggers = gameWorld.getTriggersFromKey(TriggerTypeKey.WAVE);\n        for (Trigger uncasted : waveTriggers) {\n            WaveTrigger waveTrigger = (WaveTrigger) uncasted;\n            if (((DGame) gameWorld.getGame()).getWaveKills() >= Math.ceil(gameWorld.getMobCount() * waveTrigger.getMustKillRate())) {\n                waveTrigger.trigger(true, null);\n            }\n        }\n\n        gameWorld.removeMob(this);\n    }\n\n    private boolean getDrops(Object drops) {\n        if (drops instanceof Boolean) {\n            return (Boolean) drops;\n        } else if (drops instanceof Set) {\n            for (ExMob whitelisted : (Set<ExMob>) drops) {\n                if (type.isSubsumableUnder(whitelisted)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{type=\" + type + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/DMobListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.xlib.mob.VanillaMob;\nimport org.bukkit.World;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.PiglinAbstract;\nimport org.bukkit.entity.Skeleton;\nimport org.bukkit.entity.Zombie;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.entity.CreatureSpawnEvent;\nimport org.bukkit.event.entity.EntityCombustByEntityEvent;\nimport org.bukkit.event.entity.EntityCombustEvent;\nimport org.bukkit.event.entity.EntityDeathEvent;\n\n/**\n * @author Daniel Saukel, Frank Baumann\n */\npublic class DMobListener implements Listener {\n\n    private DungeonsXL plugin;\n\n    public DMobListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    @EventHandler\n    public void onCreatureSpawn(CreatureSpawnEvent event) {\n        World world = event.getLocation().getWorld();\n\n        InstanceWorld instance = plugin.getInstanceWorld(world);\n        if (instance == null) {\n            return;\n        }\n\n        switch (event.getSpawnReason()) {\n            case CHUNK_GEN:\n            case JOCKEY:\n            case MOUNT:\n            case NATURAL:\n                event.setCancelled(true);\n                return;\n        }\n\n        VanillaMob vm = VanillaMob.get(event.getEntityType());\n        if (vm == VanillaMob.PIGLIN || vm == VanillaMob.PIGLIN_BRUTE) {\n            ((PiglinAbstract) event.getEntity()).setImmuneToZombification(true);\n        }\n    }\n\n    @EventHandler\n    public void onEntityDeath(EntityDeathEvent event) {\n        World world = event.getEntity().getWorld();\n\n        if (event.getEntity() instanceof LivingEntity) {\n            LivingEntity entity = event.getEntity();\n            GameWorld gameWorld = plugin.getGameWorld(world);\n            if (gameWorld != null) {\n                if (gameWorld.isPlaying()) {\n                    DMob dMob = (DMob) plugin.getDungeonMob(entity);\n                    if (dMob != null) {\n                        dMob.onDeath(plugin, event);\n                    }\n                }\n            }\n        }\n    }\n\n    // Prevent undead combustion from the sun.\n    @EventHandler\n    public void onEntityCombust(EntityCombustEvent event) {\n        if (event instanceof EntityCombustByEntityEvent) {\n            return;\n        }\n        Entity entity = event.getEntity();\n        if ((entity instanceof Skeleton || entity instanceof Zombie) && plugin.getGameWorld(entity.getWorld()) != null) {\n            event.setCancelled(true);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/DNPCRegistry.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport java.util.Iterator;\nimport java.util.UUID;\nimport net.citizensnpcs.Settings.Setting;\nimport net.citizensnpcs.api.CitizensAPI;\nimport net.citizensnpcs.api.event.NPCCreateEvent;\nimport net.citizensnpcs.api.npc.AbstractNPC;\nimport net.citizensnpcs.api.npc.NPC;\nimport net.citizensnpcs.api.npc.NPCRegistry;\nimport net.citizensnpcs.api.persistence.PersistenceLoader;\nimport net.citizensnpcs.api.trait.Trait;\nimport net.citizensnpcs.api.trait.trait.MobType;\nimport net.citizensnpcs.api.util.DataKey;\nimport net.citizensnpcs.api.util.MemoryDataKey;\nimport net.citizensnpcs.npc.CitizensNPC;\nimport net.citizensnpcs.npc.EntityControllers;\nimport net.citizensnpcs.trait.ArmorStandTrait;\nimport net.citizensnpcs.trait.LookClose;\nimport net.citizensnpcs.trait.MountTrait;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.EntityType;\n\n/**\n * This class folllows the implementation in Citizens, see\n * <a href=\"https://github.com/CitizensDev/Citizens2/blob/master/main/src/main/java/net/citizensnpcs/npc/CitizensNPCRegistry.java\">GitHub</a>\n *\n * @author Daniel Saukel\n */\npublic class DNPCRegistry implements NPCRegistry {\n\n    @Override\n    public NPC createNPC(EntityType type, String name) {\n        return createNPC(type, UUID.randomUUID(), 0, name);\n    }\n\n    @Override\n    public NPC createNPC(EntityType type, UUID uuid, int id, String name) {\n        NPC npc = new CitizensNPC(uuid, id, name, EntityControllers.createForType(type), this);\n        if (npc == null) {\n            throw new IllegalStateException(\"Could not create NPC: npc is null\");\n        }\n\n        Bukkit.getPluginManager().callEvent(new NPCCreateEvent(npc));\n\n        if (type == EntityType.ARMOR_STAND && !npc.hasTrait(ArmorStandTrait.class)) {\n            npc.addTrait(ArmorStandTrait.class);\n        }\n        if (Setting.DEFAULT_LOOK_CLOSE.asBoolean()) {\n            npc.addTrait(LookClose.class);\n        }\n        npc.addTrait(MountTrait.class);\n\n        return npc;\n    }\n\n    @Override\n    public void deregister(NPC npc) {\n        CitizensAPI.getNPCRegistry().deregister(npc);\n    }\n\n    @Override\n    public void deregisterAll() {\n        CitizensAPI.getNPCRegistry().deregisterAll();\n    }\n\n    @Override\n    public NPC getById(int id) {\n        return CitizensAPI.getNPCRegistry().getById(id);\n    }\n\n    @Override\n    public NPC getByUniqueId(UUID uuid) {\n        return CitizensAPI.getNPCRegistry().getByUniqueId(uuid);\n    }\n\n    @Override\n    public NPC getByUniqueIdGlobal(UUID uuid) {\n        return CitizensAPI.getNPCRegistry().getByUniqueIdGlobal(uuid);\n    }\n\n    @Override\n    public NPC getNPC(Entity entity) {\n        return CitizensAPI.getNPCRegistry().getNPC(entity);\n    }\n\n    @Override\n    public boolean isNPC(Entity entity) {\n        return CitizensAPI.getNPCRegistry().isNPC(entity);\n    }\n\n    @Override\n    public Iterable<NPC> sorted() {\n        return CitizensAPI.getNPCRegistry().sorted();\n    }\n\n    @Override\n    public Iterator<NPC> iterator() {\n        return CitizensAPI.getNPCRegistry().iterator();\n    }\n\n    /**\n     * Clones an NPC without spamming the config.\n     *\n     * @param npc the NPC to clone\n     * @return a clone of the NPC\n     */\n    public NPC createTransientClone(AbstractNPC npc) {\n        NPC copy = createNPC(npc.getTrait(MobType.class).getType(), npc.getFullName());\n        DataKey key = new MemoryDataKey();\n        save(npc, key);\n        copy.load(key);\n        for (Trait trait : copy.getTraits()) {\n            trait.onCopy();\n        }\n        return copy;\n    }\n\n    // Like in AbstractNPC#save(DataKey), but without persistence stuff\n    public void save(AbstractNPC npc, DataKey root) {\n        if (!npc.data().get(NPC.SHOULD_SAVE_METADATA, true)) {\n            return;\n        }\n        npc.data().saveTo(root.getRelative(\"metadata\"));\n        root.setString(\"name\", npc.getFullName());\n        root.setString(\"uuid\", npc.getUniqueId().toString());\n\n        StringBuilder traitNames = new StringBuilder();\n        for (Trait trait : npc.getTraits()) {\n            DataKey traitKey = root.getRelative(\"traits.\" + trait.getName());\n            trait.save(traitKey);\n            PersistenceLoader.save(trait, traitKey);\n            //npc.removedTraits.remove(trait.getName());\n            traitNames.append(trait.getName() + \",\");\n        }\n        if (traitNames.length() > 0) {\n            root.setString(\"traitnames\", traitNames.substring(0, traitNames.length() - 1));\n        } else {\n            root.setString(\"traitnames\", \"\");\n        }\n        //npc.removedTraits.clear();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/mob/ExternalMobPlugin.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.mob;\n\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\n\n/**\n * Officially supported external mob plugins.\n *\n * @author Daniel Saukel\n */\npublic enum ExternalMobPlugin implements ExternalMobProvider {\n\n    CUSTOM_MOBS(\"CM\", \"ccmob spawn %mob% %world% %block_x% %block_y% %block_z%\"),\n    INSANE_MOBS(\"IM\", \"insanemobs %mob% %x% %y% %z% %world%\"),\n    MYTHIC_MOBS(\"MM\", \"mythicmobs mobs spawn -s %mob% 1 %world%,%x%,%y%,%z%\");\n\n    private String identifier;\n    private String command;\n\n    ExternalMobPlugin(String identifier, String command) {\n        this.identifier = identifier;\n        this.command = command;\n    }\n\n    @Override\n    public String getIdentifier() {\n        return identifier;\n    }\n\n    @Override\n    public String getRawCommand() {\n        return command;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DEditPlayer.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.ProgressBar;\nimport org.bukkit.Bukkit;\nimport org.bukkit.GameMode;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.SignChangeEvent;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class DEditPlayer extends DInstancePlayer implements EditPlayer {\n\n    private String[] linesCopy;\n    private EditWorld editWorld;\n\n    public DEditPlayer(DungeonsXL plugin, Player player, EditWorld world) {\n        super(plugin, player, world);\n        editWorld = world;\n\n        // Set gamemode a few ticks later to avoid incompatibilities with plugins that force a gamemode\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                player.setGameMode(GameMode.CREATIVE);\n            }\n        }.runTaskLater(plugin, 10L);\n\n        clearPlayerData();\n\n        Location teleport = world.getLobbyLocation();\n        if (teleport == null) {\n            player.teleport(world.getWorld().getSpawnLocation());\n        } else {\n            player.teleport(teleport);\n        }\n\n        // Permission bridge\n        if (xlib.getPermissionProvider() != null) {\n            for (String permission : plugin.getMainConfig().getEditPermissions()) {\n                xlib.getPermissionProvider().playerAddTransient(getWorld().getName(), player, permission);\n            }\n        }\n    }\n\n\n    /* Getters and setters */\n    @Override\n    public EditWorld getEditWorld() {\n        return editWorld;\n    }\n\n    @Override\n    public String[] getCopiedLines() {\n        return linesCopy;\n    }\n\n    @Override\n    public void setCopiedLines(String[] linesCopy) {\n        this.linesCopy = linesCopy;\n    }\n\n    /* Actions */\n    @Override\n    public void delete() {\n        // Permission bridge\n        if (xlib.getPermissionProvider() != null) {\n            for (String permission : plugin.getMainConfig().getEditPermissions()) {\n                xlib.getPermissionProvider().playerRemoveTransient(getWorld().getName(), player, permission);\n            }\n        }\n\n        super.delete();\n    }\n\n    /**\n     * Escape the DEditWorld without saving.\n     */\n    @Override\n    public void escape() {\n        delete();\n        reset(false);\n    }\n\n    public void poke(Block block) {\n        if (block.getState() instanceof Sign) {\n            Sign sign = (Sign) block.getState();\n            String[] lines = sign.getLines();\n            if (lines[0].isEmpty() && lines[1].isEmpty() && lines[2].isEmpty() && lines[3].isEmpty()) {\n                if (linesCopy != null) {\n                    SignChangeEvent event = new SignChangeEvent(block, getPlayer(), linesCopy);\n                    Bukkit.getPluginManager().callEvent(event);\n                    if (!event.isCancelled()) {\n                        sign.setLine(0, event.getLine(0));\n                        sign.setLine(1, event.getLine(1));\n                        sign.setLine(2, event.getLine(2));\n                        sign.setLine(3, event.getLine(3));\n                        sign.update();\n                    }\n                }\n            } else {\n                linesCopy = lines;\n                MessageUtil.sendMessage(getPlayer(), DMessage.PLAYER_SIGN_COPIED.getMessage());\n            }\n        } else {\n            String info = \"\" + block.getType();\n            if (block.getData() != 0) {\n                info = info + \",\" + block.getData();\n            }\n            MessageUtil.sendMessage(getPlayer(), DMessage.PLAYER_BLOCK_INFO.getMessage(info));\n        }\n    }\n\n    @Override\n    public void leave(boolean unloadIfEmpty) {\n        delete();\n\n        reset(false);\n\n        if (unloadIfEmpty && !plugin.isLoadingWorld() && editWorld != null && editWorld.getPlayers().isEmpty()) {\n            plugin.setLoadingWorld(true);\n            editWorld.delete();\n            new ProgressBar(player, config.getEditInstanceRemovalDelay()) {\n                @Override\n                public void onFinish() {\n                    MessageUtil.sendActionBarMessage(player, DMessage.CMD_SAVE_SUCCESS.getMessage());\n                    plugin.setLoadingWorld(false);\n                }\n            }.send(plugin);\n        }\n    }\n\n    @Override\n    public void update() {\n        if (player.getWorld().equals(getWorld())) {\n            return;\n        }\n        MessageUtil.debug(plugin, \"Player \" + this + \" was expected to be in \" + getWorld() + \" but is in \" + player.getWorld());\n        Location teleportLocation;\n        if (editWorld.getLobbyLocation() != null) {\n            teleportLocation = editWorld.getLobbyLocation();\n        } else {\n            teleportLocation = getWorld().getSpawnLocation();\n        }\n\n        MessageUtil.debug(plugin, \"Teleporting to \" + teleportLocation);\n        player.teleport(teleportLocation);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameGoal;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerKickEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupScoreEvent;\nimport de.erethon.dungeonsxl.api.event.player.GamePlayerDeathEvent;\nimport de.erethon.dungeonsxl.api.event.player.GamePlayerFinishEvent;\nimport de.erethon.dungeonsxl.api.event.player.GlobalPlayerRewardPayOutEvent;\nimport de.erethon.dungeonsxl.api.event.requirement.RequirementDemandEvent;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerClass;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.trigger.DistanceTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.dungeonsxl.world.block.TeamFlag;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.mob.VanillaMob;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.Bukkit;\nimport org.bukkit.GameMode;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Damageable;\nimport org.bukkit.entity.EntityType;\nimport org.bukkit.entity.ExperienceOrb;\nimport org.bukkit.entity.Player;\nimport org.bukkit.entity.Wolf;\nimport org.bukkit.event.entity.PlayerDeathEvent;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.potion.PotionEffect;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Tobias Schmitz, Milan Albrecht, Daniel Saukel\n */\npublic class DGamePlayer extends DInstancePlayer implements GamePlayer {\n\n    private DGroup dGroup;\n\n    private boolean ready = false;\n    private boolean finished = false;\n\n    private PlayerClass dClass;\n    private Location checkpoint;\n    private Wolf wolf;\n    private int wolfRespawnTime = 30;\n    private long offlineTime;\n    private boolean resetClassInventoryOnRespawn;\n\n    private int initialLives = -1;\n    private int lives;\n\n    private ItemStack oldHelmet;\n    private PlayerGroup stealing;\n\n    private DGroupTag groupTag;\n\n    public DGamePlayer(DungeonsXL plugin, Player player, GameWorld world) {\n        super(plugin, player, world);\n\n        Game game = world.getGame();\n        dGroup = (DGroup) plugin.getPlayerGroup(player);\n        GameRuleContainer rules = game.getRules();\n        player.setGameMode(GameMode.SURVIVAL);\n\n        if (!rules.getState(GameRule.KEEP_INVENTORY_ON_ENTER)) {\n            clearPlayerData();\n        }\n        player.setAllowFlight(rules.getState(GameRule.FLY));\n\n        initialLives = rules.getState(GameRule.INITIAL_LIVES);\n        lives = initialLives;\n\n        resetClassInventoryOnRespawn = rules.getState(GameRule.RESET_CLASS_INVENTORY_ON_RESPAWN);\n\n        if (rules.getState(GameRule.GROUP_TAG_ENABLED)) {\n            initDGroupTag();\n        }\n\n        Location teleport = world.getLobbyLocation();\n        if (teleport == null) {\n            player.teleport(world.getWorld().getSpawnLocation());\n        } else {\n            player.teleport(teleport);\n        }\n\n        if (!((DGameWorld) world).hasReadySign()) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_READY_SIGN.getMessage(world.getName()));\n        }\n    }\n\n    /* Getters and setters */\n    @Override\n    public String getName() {\n        String name = player.getName();\n        if (getGroup() != null && dGroup.getDColor() != null) {\n            name = getGroup().getDColor().getChatColor() + name;\n        }\n        return name;\n    }\n\n    @Override\n    public DGroup getGroup() {\n        return dGroup;\n    }\n\n    public void setPlayer(Player player) {\n        this.player = player;\n    }\n\n    /**\n     * @return if the player is in test mode\n     */\n    public boolean isInTestMode() {\n        if (getGroup() == null) {\n            return false;\n        }\n\n        if (getGame() == null) {\n            return false;\n        }\n\n        return getGame().hasRewards();\n    }\n\n    @Override\n    public boolean isReady() {\n        return ready;\n    }\n\n    public void setReady(boolean ready) {\n        this.ready = ready;\n    }\n\n    @Override\n    public boolean isFinished() {\n        return finished;\n    }\n\n    @Override\n    public void setFinished(boolean finished) {\n        this.finished = finished;\n    }\n\n    @Override\n    public PlayerClass getPlayerClass() {\n        return dClass;\n    }\n\n    @Override\n    public void setPlayerClass(PlayerClass playerClass) {\n        dClass = playerClass;\n        if (dClass == null) {\n            return;\n        }\n\n        /* Set Dog */\n        if (wolf != null) {\n            wolf.remove();\n            wolf = null;\n        }\n\n        if (dClass.hasDog()) {\n            wolf = (Wolf) getWorld().spawnEntity(getPlayer().getLocation(), EntityType.WOLF);\n            wolf.setTamed(true);\n            wolf.setOwner(getPlayer());\n\n            double maxHealth = ((Damageable) wolf).getMaxHealth();\n            wolf.setHealth(maxHealth);\n        }\n\n        /* Delete Inventory */\n        getPlayer().getInventory().clear();\n        getPlayer().getInventory().setArmorContents(null);\n        getPlayer().getInventory().setItemInHand(VanillaItem.AIR.toItemStack());\n\n        // Remove Potion Effects\n        for (PotionEffect effect : getPlayer().getActivePotionEffects()) {\n            getPlayer().removePotionEffect(effect.getType());\n        }\n\n        // Reset lvl\n        getPlayer().setExp(0f);\n        getPlayer().setLevel(0);\n\n        /* Set Inventory */\n        for (ItemStack istack : dClass.getItems()) {\n\n            // Leggings\n            if (VanillaItem.LEATHER_LEGGINGS.is(istack) || VanillaItem.CHAINMAIL_LEGGINGS.is(istack) || VanillaItem.IRON_LEGGINGS.is(istack)\n                    || VanillaItem.DIAMOND_LEGGINGS.is(istack) || VanillaItem.GOLDEN_LEGGINGS.is(istack)) {\n                getPlayer().getInventory().setLeggings(istack);\n            } // Helmet\n            else if (VanillaItem.LEATHER_HELMET.is(istack) || VanillaItem.CHAINMAIL_HELMET.is(istack) || VanillaItem.IRON_HELMET.is(istack)\n                    || VanillaItem.DIAMOND_HELMET.is(istack) || VanillaItem.GOLDEN_HELMET.is(istack)) {\n                getPlayer().getInventory().setHelmet(istack);\n            } // Chestplate\n            else if (VanillaItem.LEATHER_CHESTPLATE.is(istack) || VanillaItem.CHAINMAIL_CHESTPLATE.is(istack) || VanillaItem.IRON_CHESTPLATE.is(istack)\n                    || VanillaItem.DIAMOND_CHESTPLATE.is(istack) || VanillaItem.GOLDEN_CHESTPLATE.is(istack)) {\n                getPlayer().getInventory().setChestplate(istack);\n            } // Boots\n            else if (VanillaItem.LEATHER_BOOTS.is(istack) || VanillaItem.CHAINMAIL_BOOTS.is(istack) || VanillaItem.IRON_BOOTS.is(istack)\n                    || VanillaItem.DIAMOND_BOOTS.is(istack) || VanillaItem.GOLDEN_BOOTS.is(istack)) {\n                getPlayer().getInventory().setBoots(istack);\n            } else {\n                getPlayer().getInventory().addItem(istack);\n            }\n        }\n    }\n\n    @Override\n    public Location getLastCheckpoint() {\n        return checkpoint;\n    }\n\n    @Override\n    public void setLastCheckpoint(Location checkpoint) {\n        this.checkpoint = checkpoint;\n    }\n\n    @Override\n    public long getOfflineTimeMillis() {\n        return offlineTime;\n    }\n\n    @Override\n    public void setOfflineTimeMillis(long offlineTime) {\n        this.offlineTime = offlineTime;\n    }\n\n    @Override\n    public int getInitialLives() {\n        return initialLives;\n    }\n\n    @Override\n    public void setInitialLives(int initialLives) {\n        this.initialLives = initialLives;\n    }\n\n    @Override\n    public int getLives() {\n        return lives;\n    }\n\n    @Override\n    public void setLives(int lives) {\n        this.lives = lives;\n    }\n\n    @Override\n    public Wolf getWolf() {\n        return wolf;\n    }\n\n    @Override\n    public void setWolf(Wolf wolf) {\n        this.wolf = wolf;\n    }\n\n    public int getWolfRespawnTime() {\n        return wolfRespawnTime;\n    }\n\n    public void setWolfRespawnTime(int wolfRespawnTime) {\n        this.wolfRespawnTime = wolfRespawnTime;\n    }\n\n    @Override\n    public boolean isStealingFlag() {\n        return stealing != null;\n    }\n\n    @Override\n    public PlayerGroup getRobbedGroup() {\n        return stealing;\n    }\n\n    @Override\n    public void setRobbedGroup(PlayerGroup group) {\n        if (group != null) {\n            oldHelmet = player.getInventory().getHelmet();\n            player.getInventory().setHelmet(getGroup().getDColor().getWoolMaterial().toItemStack());\n        }\n\n        stealing = group;\n    }\n\n    public DGroupTag getDGroupTag() {\n        return groupTag;\n    }\n\n    public void initDGroupTag() {\n        groupTag = new DGroupTag(plugin, this);\n    }\n\n    /* Actions */\n    @Override\n    public void captureFlag() {\n        if (stealing == null) {\n            return;\n        }\n\n        Game game = plugin.getGame(getWorld());\n        if (game == null) {\n            return;\n        }\n\n        GroupScoreEvent event = new GroupScoreEvent(getGroup(), this, stealing);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        game.sendMessage(DMessage.GROUP_FLAG_CAPTURED.getMessage(getName(), stealing.getName()));\n\n        GameRuleContainer rules = game.getRules();\n\n        getGroup().setScore(getGroup().getScore() + 1);\n        GameGoal goal = rules.getState(GameRule.GAME_GOAL);\n        if (goal.getType().hasComponent(GameGoal.SCORE_GOAL) && goal.getState(GameGoal.SCORE_GOAL) == getGroup().getScore()) {\n            getGroup().winGame();\n        }\n\n        stealing.setScore(stealing.getScore() - 1);\n        if (stealing.getScore() == -1) {\n            for (GamePlayer member : ((DGroup) stealing).getDGamePlayers()) {\n                member.kill();\n            }\n            game.sendMessage(DMessage.GROUP_DEFEATED.getMessage(stealing.getName()));\n        }\n\n        stealing = null;\n        player.getInventory().setHelmet(oldHelmet);\n\n        if (game.getGroups().size() == 1) {\n            dGroup.winGame();\n        }\n    }\n\n    @Override\n    public void leave() {\n        leave(true);\n    }\n\n    @Override\n    public void leave(boolean message) {\n        if (getGame() == null) {\n            return;\n        }\n        MessageUtil.assertTrue(\"GameWorld must not be null\", getGameWorld(), w -> w != null);\n        GameRuleContainer rules = getGame().getRules();\n        delete();\n\n        if (player.isOnline()) {\n            reset(finished);\n        }\n\n        // Permission bridge\n        if (xlib.getPermissionProvider() != null) {\n            for (String permission : rules.getState(GameRule.GAME_PERMISSIONS)) {\n                xlib.getPermissionProvider().playerRemoveTransient(getWorld().getName(), player, permission);\n            }\n        }\n\n        if (getGroup() != null) {\n            dGroup.removeMember(getPlayer(), message);\n        }\n\n        if (getGame() != null && finished && getGame().hasRewards()) {\n            MessageUtil.debug(plugin, \"Rewarding \" + this);\n            reward();\n        }\n\n        if (getGroup() != null) {\n            if (!dGroup.isEmpty()) {\n                /*if (dGroup.finishIfMembersFinished()) {\n                    return;\n                }*/\n\n                // Give secure objects to other players\n                Player groupPlayer = null;\n                for (Player player : dGroup.getMembers().getOnlinePlayers()) {\n                    if (player.isOnline()) {\n                        groupPlayer = player;\n                        break;\n                    }\n                }\n                if (groupPlayer != null) {\n                    for (ItemStack itemStack : getPlayer().getInventory()) {\n                        if (itemStack != null) {\n                            if (((DGameWorld) getGameWorld()).getSecureObjects().contains(itemStack)) {\n                                groupPlayer.getInventory().addItem(itemStack);\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (dGroup.getLeader().equals(getPlayer()) && dGroup.getMembers().size() > 0) {\n                // Captain here!\n                Player newCaptain = null;\n                for (Player player : dGroup.getMembers().getOnlinePlayers()) {\n                    if (player.isOnline()) {\n                        newCaptain = player;\n                        break;\n                    }\n                }\n                dGroup.setLeader(newCaptain);\n                if (message) {\n                    MessageUtil.sendMessage(newCaptain, DMessage.PLAYER_NEW_LEADER.getMessage());\n                }\n                // ...*flies away*\n            }\n        }\n\n        if (getGameWorld().getPlayers().isEmpty()) {\n            getGameWorld().delete();\n        }\n    }\n\n    public void reward() {\n        List<Reward> rewards = new ArrayList<>(dGroup.getRewards());\n        rewards.addAll(getGame().getRules().getState(GameRule.REWARDS));\n        GlobalPlayerRewardPayOutEvent globalPlayerPayOutRewardEvent = new GlobalPlayerRewardPayOutEvent(this, rewards);\n        Bukkit.getPluginManager().callEvent(globalPlayerPayOutRewardEvent);\n        if (!globalPlayerPayOutRewardEvent.isCancelled()) {\n            giveLoot(getGroup().getDungeon(), globalPlayerPayOutRewardEvent.getRewards());\n        }\n\n        getData().logTimeLastFinished(getGroup().getDungeonName());\n\n        // Tutorial Permissions\n        if (!getGame().isTutorial()) {\n            return;\n        }\n        getData().setFinishedTutorial(true);\n        if (xlib.getPermissionProvider() != null && xlib.getPermissionProvider().hasGroupSupport()) {\n            String endGroup = plugin.getMainConfig().getTutorialEndGroup();\n            if (xlib.isGroupEnabled(endGroup)) {\n                xlib.getPermissionProvider().playerAddGroup(getPlayer(), endGroup);\n            }\n\n            String startGroup = plugin.getMainConfig().getTutorialStartGroup();\n            if (xlib.isGroupEnabled(startGroup)) {\n                xlib.getPermissionProvider().playerRemoveGroup(getPlayer(), startGroup);\n            }\n        }\n    }\n\n    @Override\n    public void kill() {\n        GroupPlayerKickEvent dPlayerKickEvent = new GroupPlayerKickEvent(getGroup(), this, GroupPlayerKickEvent.Cause.DEATH);\n        Bukkit.getPluginManager().callEvent(dPlayerKickEvent);\n\n        if (!dPlayerKickEvent.isCancelled()) {\n            GameWorld gameWorld = getGroup().getGameWorld();\n            if (lives != -1) {\n                gameWorld.sendMessage(DMessage.PLAYER_DEATH_KICK.getMessage(getName()));\n            } else if (getGroup().getLives() != -1) {\n                gameWorld.sendMessage(DMessage.GROUP_DEATH_KICK.getMessage(getName(), dGroup.getName()));\n            }\n\n            leave();\n        }\n    }\n\n    @Override\n    public boolean ready() {\n        if (dGroup == null) {\n            return false;\n        }\n\n        Game game = dGroup.getGame();\n        if (game == null) {\n            game = new DGame(plugin, dGroup.getDungeon(), dGroup);\n        }\n\n        if (game.hasRewards() && !checkRequirements(game.getDungeon())) {\n            return false;\n        }\n\n        ready = true;\n        return ready;\n    }\n\n    @Override\n    public void respawn() {\n        Location respawn = checkpoint;\n\n        if (respawn == null) {\n            respawn = getGroup().getGameWorld().getStartLocation(dGroup);\n        }\n\n        if (respawn == null) {\n            respawn = getWorld().getSpawnLocation();\n        }\n\n        getPlayer().teleport(respawn);\n\n        if (resetClassInventoryOnRespawn) {\n            setPlayerClass(dClass);\n        } else {\n            // Don't forget Doge!\n            if (wolf != null) {\n                wolf.teleport(getPlayer());\n            }\n        }\n    }\n\n    public void startGame() {\n        Dungeon dungeon = getGame().getDungeon();\n        GameRuleContainer rules = dungeon.getRules();\n        getData().logTimeLastStarted(dungeon.getName());\n        getData().setKeepInventoryAfterLogout(rules.getState(GameRule.KEEP_INVENTORY_ON_ESCAPE));\n\n        respawn();\n\n        if (plugin.getMainConfig().isSendFloorTitleEnabled()) {\n            String mapName = getGameWorld().getName();\n            if (rules.getState(GameRule.TITLE) != null || rules.getState(GameRule.SUBTITLE) != null) {\n                String title = rules.getState(GameRule.TITLE) == null ? \"\" : rules.getState(GameRule.TITLE);\n                String subtitle = rules.getState(GameRule.SUBTITLE) == null ? \"\" : rules.getState(GameRule.SUBTITLE);\n\n                MessageUtil.sendTitleMessage(player, title, subtitle,\n                        rules.getState(GameRule.TITLE_FADE_IN), rules.getState(GameRule.TITLE_SHOW), rules.getState(GameRule.TITLE_FADE_OUT));\n\n            } else if (!dungeon.getName().equals(mapName)) {\n                MessageUtil.sendTitleMessage(player, \"&b&l\" + dungeon.getName().replaceAll(\"_\", \" \"), \"&4&l\" + mapName.replaceAll(\"_\", \" \"));\n\n            } else {\n                MessageUtil.sendTitleMessage(player, \"&4&l\" + mapName.replaceAll(\"_\", \" \"));\n            }\n\n            if (rules.getState(GameRule.ACTION_BAR) != null) {\n                MessageUtil.sendActionBarMessage(player, rules.getState(GameRule.ACTION_BAR));\n            }\n\n            if (rules.getState(GameRule.CHAT) != null) {\n                MessageUtil.sendCenteredMessage(player, rules.getState(GameRule.CHAT));\n            }\n        }\n\n        for (Requirement requirement : rules.getState(GameRule.REQUIREMENTS)) {\n            RequirementDemandEvent requirementDemandEvent\n                    = new RequirementDemandEvent(requirement, dungeon, player, rules.getState(GameRule.KEEP_INVENTORY_ON_ENTER));\n            Bukkit.getPluginManager().callEvent(requirementDemandEvent);\n            if (requirementDemandEvent.isCancelled()) {\n                continue;\n            }\n\n            if (!DPermission.hasPermission(player, DPermission.IGNORE_REQUIREMENTS)) {\n                requirement.demand(player);\n            }\n        }\n\n        player.setGameMode(rules.getState(GameRule.GAME_MODE));\n\n        // Permission bridge\n        if (xlib.getPermissionProvider() != null) {\n            for (String permission : rules.getState(GameRule.GAME_PERMISSIONS)) {\n                xlib.getPermissionProvider().playerAddTransient(getGame().getWorld().getWorld().getName(), player, permission);\n            }\n        }\n    }\n\n    /**\n     * The DGamePlayer finishs the current floor.\n     *\n     * @param specifiedFloor the name of the next floor\n     */\n    public void finishFloor(DResourceWorld specifiedFloor) {\n        if (!dGroup.getDungeon().isMultiFloor()) {\n            finish();\n            return;\n        }\n\n        MessageUtil.sendMessage(getPlayer(), DMessage.PLAYER_FINISHED_FLOOR.getMessage());\n        finished = true;\n\n        boolean hasToWait = false;\n        if (getGroup() == null) {\n            return;\n        }\n        if (!dGroup.isPlaying()) {\n            return;\n        }\n        getGame().setNextFloor(specifiedFloor);\n        if (dGroup.isFinished()) {\n            dGroup.finishFloor(specifiedFloor);\n        } else {\n            MessageUtil.sendMessage(player, DMessage.PLAYER_WAIT_FOR_OTHER_PLAYERS.getMessage());\n            hasToWait = true;\n        }\n\n        GamePlayerFinishEvent gamePlayerFinishEvent = new GamePlayerFinishEvent(this, hasToWait);\n        Bukkit.getPluginManager().callEvent(gamePlayerFinishEvent);\n        if (gamePlayerFinishEvent.isCancelled()) {\n            finished = false;\n        }\n    }\n\n    @Override\n    public void finish() {\n        finish(true);\n    }\n\n    @Override\n    public void finish(boolean message) {\n        if (message) {\n            MessageUtil.sendMessage(getPlayer(), DMessage.PLAYER_FINISHED_DUNGEON.getMessage());\n        }\n        finished = true;\n\n        boolean hasToWait = false;\n        if (!getGroup().isPlaying()) {\n            return;\n        }\n        if (dGroup.isFinished()) {\n            dGroup.finish();\n        } else {\n            if (message) {\n                MessageUtil.sendMessage(this.getPlayer(), DMessage.PLAYER_WAIT_FOR_OTHER_PLAYERS.getMessage());\n            }\n            hasToWait = true;\n        }\n\n        GamePlayerFinishEvent gamePlayerFinishEvent = new GamePlayerFinishEvent(this, hasToWait);\n        Bukkit.getPluginManager().callEvent(gamePlayerFinishEvent);\n        if (gamePlayerFinishEvent.isCancelled()) {\n            finished = false;\n        }\n    }\n\n    public void onDeath(PlayerDeathEvent event) {\n        DGameWorld gameWorld = (DGameWorld) getGameWorld();\n        if (gameWorld == null) {\n            return;\n        }\n\n        DGame game = (DGame) getGame();\n        if (game == null) {\n            return;\n        }\n        GameRuleContainer rules = game.getRules();\n        boolean keepInventory = rules.getState(GameRule.KEEP_INVENTORY_ON_DEATH);\n\n        GamePlayerDeathEvent dPlayerDeathEvent = new GamePlayerDeathEvent(this, keepInventory, 1);\n        Bukkit.getPluginManager().callEvent(dPlayerDeathEvent);\n        if (dPlayerDeathEvent.isCancelled()) {\n            return;\n        }\n\n        Location deathLocation = player.getLocation();\n        if (keepInventory && event != null) {\n            event.setKeepInventory(true);\n            event.getDrops().clear();\n            event.setKeepLevel(true);\n            event.setDroppedExp(0);\n        } else if (!keepInventory && event == null) {\n            for (ItemStack itemStack : player.getInventory().getContents()) {\n                if (itemStack == null) {\n                    continue;\n                }\n                getWorld().dropItemNaturally(deathLocation, itemStack);\n            }\n            player.getInventory().clear();\n\n            ExperienceOrb orb = (ExperienceOrb) VanillaMob.EXPERIENCE_ORB.toEntity(deathLocation);\n            int expDrop = 7 * player.getLevel();\n            expDrop = expDrop > 100 ? 100 : expDrop;\n            orb.setExperience(expDrop);\n            int newLevel = player.getLevel() - 7;\n            player.setLevel(newLevel > 0 ? newLevel : 0);\n            player.setExp(0);\n        }\n\n        if (event == null) {\n            Location respawn = getLastCheckpoint();\n            if (respawn == null) {\n                respawn = gameWorld.getStartLocation(getGroup());\n            }\n            player.teleport(respawn);\n            heal();\n            if (getWolf() != null) {\n                getWolf().teleport(respawn);\n            }\n\n            if (rules.getState(GameRule.RESET_CLASS_INVENTORY_ON_RESPAWN)) {\n                setPlayerClass(dClass);\n            }\n\n        } else if (config.areGlobalDeathMessagesDisabled()) {\n            event.setDeathMessage(null);\n        }\n\n        if (getGroup() != null && dGroup.getLives() != -1) {\n            int newLives = dGroup.getLives() - dPlayerDeathEvent.getLostLives();\n            dGroup.setLives(newLives < 0 ? 0 : newLives);// If the group already has 0 lives, don't remove any\n            gameWorld.sendMessage(DMessage.GROUP_DEATH.getMessage(getName(), dGroup.getName(), String.valueOf(dGroup.getLives())));\n\n        } else {\n            if (lives != -1) {\n                lives = lives - dPlayerDeathEvent.getLostLives();\n            }\n\n            GamePlayer killer = plugin.getPlayerCache().getGamePlayer(player.getKiller());\n            String newLives = lives == -1 ? DMessage.PLAYER_UNLIMITED_LIVES.getMessage() : String.valueOf(this.lives);\n            if (killer != null) {\n                gameWorld.sendMessage(DMessage.PLAYER_KILLED.getMessage(getName(), killer.getName(), newLives));\n            } else {\n                gameWorld.sendMessage(DMessage.PLAYER_DEATH.getMessage(getName(), newLives));\n            }\n        }\n\n        if (isStealingFlag()) {\n            for (TeamFlag teamFlag : gameWorld.getTeamFlags()) {\n                if (teamFlag.getOwner().equals(stealing)) {\n                    teamFlag.reset();\n                    gameWorld.sendMessage(DMessage.GROUP_FLAG_LOST.getMessage(player.getName(), stealing.getName()));\n                    stealing = null;\n                }\n            }\n        }\n\n        if ((dGroup.getLives() == 0 || lives == 0) && ready) {\n            new BukkitRunnable() {\n                @Override\n                public void run() {\n                    kill();\n                }\n            }.runTaskLater(plugin, 1L);\n        }\n\n        if (rules.getState(GameRule.GAME_GOAL).getType() == GameGoal.Type.LAST_MAN_STANDING) {\n            if (game.getGroups().size() == 1) {\n                ((DGroup) game.getGroups().get(0)).winGame();\n            }\n        }\n    }\n\n    @Override\n    public void update() {\n        if (player.isOnline()) {\n            if (!player.getWorld().equals(getWorld())) {\n                MessageUtil.debug(plugin, \"Player \" + this + \" was expected to be in \" + getWorld() + \" but is in \" + player.getWorld());\n                Location teleportLocation = getLastCheckpoint();\n                if (teleportLocation == null) {\n                    teleportLocation = getGameWorld().getStartLocation(getGroup());\n                }\n                player.teleport(teleportLocation);\n                if (wolf != null && !wolf.isDead()) {\n                    wolf.teleport(teleportLocation);\n                }\n            }\n\n            if (wolf != null && wolf.isDead()) {\n                if (getWolfRespawnTime() <= 0) {\n                    wolf = (Wolf) getWorld().spawnEntity(player.getLocation(), EntityType.WOLF);\n                    wolf.setTamed(true);\n                    wolf.setOwner(player);\n                    setWolfRespawnTime(30);\n                } else {\n                    wolfRespawnTime--;\n                }\n            }\n        }\n\n        // Kick offline players\n        if (getOfflineTimeMillis() > 0 && getOfflineTimeMillis() < System.currentTimeMillis()) {\n            GroupPlayerKickEvent groupPlayerKickEvent = new GroupPlayerKickEvent(getGroup(), this, GroupPlayerKickEvent.Cause.OFFLINE);\n            Bukkit.getPluginManager().callEvent(groupPlayerKickEvent);\n            if (!groupPlayerKickEvent.isCancelled()) {\n                MessageUtil.debug(plugin, this + \" was kicked from their group for being offline for too long.\");\n                leave();\n                return;\n            }\n        }\n\n        MessageUtil.assertTrue(\"Player \" + this + \" was expected to be online but is offline.\", player, Player::isOnline);\n        DistanceTrigger.triggerAllInDistance(player, (DGameWorld) getGameWorld());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DGlobalPlayer.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent.Cause;\nimport de.erethon.dungeonsxl.api.event.requirement.RequirementCheckEvent;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.global.DPortal;\nimport de.erethon.dungeonsxl.util.AttributeUtil;\nimport de.erethon.dungeonsxl.util.LocationString;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.attribute.Attribute;\nimport org.bukkit.attribute.AttributeInstance;\nimport org.bukkit.attribute.AttributeModifier;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.potion.PotionEffect;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class DGlobalPlayer implements GlobalPlayer {\n\n    protected DungeonsXL plugin;\n    protected XLib xlib;\n\n    protected boolean is1_9 = Version.isAtLeast(Version.MC1_9);\n\n    protected Player player;\n\n    private DPlayerData data;\n\n    private boolean breakMode = false;\n    private boolean groupChat = false;\n    private boolean chatSpyMode = false;\n    private DPortal creatingPortal;\n    private ItemStack cachedItem;\n    private boolean announcerEnabled = true;\n\n    private List<ItemStack> rewardItems;\n\n    public DGlobalPlayer(DungeonsXL plugin, Player player) {\n        this(plugin, player, false);\n    }\n\n    public DGlobalPlayer(DungeonsXL plugin, Player player, boolean reset) {\n        this.plugin = plugin;\n        xlib = plugin.getXLib();\n\n        this.player = player;\n\n        loadPlayerData(new File(DungeonsXL.PLAYERS, player.getUniqueId().toString() + \".yml\"));\n        if (reset && data.wasInGame()) {\n            reset(false);\n        }\n\n        plugin.getPlayerCache().add(player, this);\n    }\n\n    public DGlobalPlayer(DGlobalPlayer dPlayer) {\n        plugin = dPlayer.plugin;\n        player = dPlayer.getPlayer();\n        data = dPlayer.getData();\n        breakMode = dPlayer.isInBreakMode();\n        chatSpyMode = dPlayer.isInChatSpyMode();\n        creatingPortal = dPlayer.getPortal();\n        announcerEnabled = dPlayer.isAnnouncerEnabled();\n\n        plugin.getPlayerCache().add(player, this);\n    }\n\n    /* Getters and setters */\n    @Override\n    public String getName() {\n        return player.getName();\n    }\n\n    @Override\n    public Player getPlayer() {\n        return player;\n    }\n\n    @Override\n    public UUID getUniqueId() {\n        return player.getUniqueId();\n    }\n\n    /**\n     * Returns the saved data.\n     *\n     * @return the saved data\n     */\n    public DPlayerData getData() {\n        return data;\n    }\n\n    /**\n     * Load / reload a new instance of DPlayerData.\n     *\n     * @param file the file to load from\n     */\n    public void loadPlayerData(File file) {\n        data = new DPlayerData(file);\n    }\n\n    @Override\n    public PlayerGroup getGroup() {\n        return plugin.getPlayerGroup(player);\n    }\n\n    @Override\n    public boolean isInGroupChat() {\n        if (!plugin.getMainConfig().isChatEnabled()) {\n            return false;\n        }\n        return groupChat;\n    }\n\n    @Override\n    public void setInGroupChat(boolean groupChat) {\n        this.groupChat = groupChat;\n    }\n\n    @Override\n    public boolean isInChatSpyMode() {\n        if (!plugin.getMainConfig().isChatEnabled()) {\n            return false;\n        }\n        return chatSpyMode;\n    }\n\n    @Override\n    public void setInChatSpyMode(boolean chatSpyMode) {\n        this.chatSpyMode = chatSpyMode;\n    }\n\n    @Override\n    public boolean isInBreakMode() {\n        return breakMode;\n    }\n\n    @Override\n    public void setInBreakMode(boolean breakMode) {\n        this.breakMode = breakMode;\n    }\n\n    /**\n     * Returns if the player is creating a DPortal.\n     *\n     * @return if the player is creating a DPortal\n     */\n    public boolean isCreatingPortal() {\n        return creatingPortal != null;\n    }\n\n    /**\n     * Returns the portal the player is creating.\n     *\n     * @return the portal the player is creating\n     */\n    public DPortal getPortal() {\n        return creatingPortal;\n    }\n\n    /**\n     * Sets the portal the player is creating.\n     *\n     * @param dPortal the portal to create\n     */\n    public void setCreatingPortal(DPortal dPortal) {\n        creatingPortal = dPortal;\n    }\n\n    /**\n     * Returns the item the player had in his hand before he started to create a portal.\n     *\n     * @return the item the player had in his hand before he started to create a portal\n     */\n    public ItemStack getCachedItem() {\n        return cachedItem;\n    }\n\n    /**\n     * Sets the cached item.\n     *\n     * @param item the cached item to set\n     */\n    public void setCachedItem(ItemStack item) {\n        cachedItem = item;\n    }\n\n    /**\n     * Returns if the player has announcers enabled.\n     *\n     * @return if the players receives announcer messages\n     */\n    public boolean isAnnouncerEnabled() {\n        return announcerEnabled;\n    }\n\n    /**\n     * Sets if the player has announcers enabled.\n     *\n     * @param enabled set if the players receives announcer messages\n     */\n    public void setAnnouncerEnabled(boolean enabled) {\n        announcerEnabled = enabled;\n    }\n\n    @Override\n    public List<ItemStack> getRewardItems() {\n        return rewardItems;\n    }\n\n    @Override\n    public boolean hasRewardItemsLeft() {\n        return rewardItems != null;\n    }\n\n    @Override\n    public void setRewardItems(List<ItemStack> rewardItems) {\n        this.rewardItems = rewardItems;\n    }\n\n    @Override\n    public boolean hasPermission(String permission) {\n        return player.hasPermission(permission);\n    }\n\n    public boolean hasPermission(DPermission permission) {\n        return DPermission.hasPermission(player, permission);\n    }\n\n    @Override\n    public boolean checkRequirements(Dungeon dungeon) {\n        boolean fulfilled = true;\n        GameRuleContainer rules = dungeon.getRules();\n\n        List<BaseComponent[]> msgs = new ArrayList<>(rules.getState(GameRule.REQUIREMENTS).size());\n        for (Requirement requirement : rules.getState(GameRule.REQUIREMENTS)) {\n            if (requirement == null) {\n                MessageUtil.log(plugin, \"The dungeon \" + dungeon.getName() + \" has an invalid requirement.\");\n                continue;\n            }\n            RequirementCheckEvent event = new RequirementCheckEvent(requirement, dungeon, player, rules.getState(GameRule.KEEP_INVENTORY_ON_ENTER));\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                continue;\n            }\n\n            msgs.add(event.getCheckMessage());\n            if (!requirement.check(player)) {\n                fulfilled = false;\n            }\n        }\n        if (!fulfilled) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_REQUIREMENTS.getMessage());\n            msgs.forEach(msg -> player.spigot().sendMessage(msg));\n        }\n\n        return fulfilled || DPermission.hasPermission(player, DPermission.IGNORE_REQUIREMENTS);\n    }\n\n    public void giveLoot(Dungeon dungeon, List<Reward> rewards) {\n        if (!canLoot(dungeon)) {\n            return;\n        }\n        rewards.forEach(r -> r.giveTo(player.getPlayer()));\n        if (dungeon != null) {\n            getData().logTimeLastLoot(dungeon.getName());\n        }\n    }\n\n    public boolean canLoot(Dungeon dungeon) {\n        return getTimeNextLoot(dungeon) <= getData().getTimeLastStarted(dungeon.getName());\n    }\n\n    public long getTimeNextLoot(Dungeon dungeon) {\n        return dungeon.getRules().getState(GameRule.TIME_TO_NEXT_LOOT) * 60 * 60 * 1000 + getData().getTimeLastLoot(dungeon.getName());\n    }\n\n    /* Actions */\n    @Override\n    public void sendMessage(String message) {\n        MessageUtil.sendMessage(player, message);\n    }\n\n    public void heal() {\n        if (is1_9) {\n            player.setHealth(player.getAttribute(AttributeUtil.MAX_HEALTH).getValue());\n        } else {\n            player.setHealth(player.getMaxHealth());\n        }\n        player.setFoodLevel(20);\n        player.setFireTicks(0);\n        for (PotionEffect effect : player.getActivePotionEffects()) {\n            player.removePotionEffect(effect.getType());\n        }\n    }\n\n    @Override\n    public void reset(boolean gameFinished) {\n        Location tpLoc;\n        boolean keepInventory;\n        if (getGroup() != null) {\n            Dungeon dungeon = getGroup().getDungeon();\n            GameRuleContainer rules = dungeon.getRules();\n            keepInventory = rules.getState(gameFinished ? GameRule.KEEP_INVENTORY_ON_FINISH : GameRule.KEEP_INVENTORY_ON_ESCAPE);\n            LocationString tpLocString = LocationString.fromString(rules.getState(gameFinished ? GameRule.FINISH_LOCATION : GameRule.ESCAPE_LOCATION));\n            if (tpLocString != null && tpLocString.getLocation() != null) {\n                tpLoc = tpLocString.getLocation();\n            } else {\n                if (data.getOldLocation().getWorld() != null) {\n                    tpLoc = data.getOldLocation();\n                } else {\n                    tpLoc = Bukkit.getWorlds().get(0).getSpawnLocation();\n                }\n            }\n\n        } else {\n            if (data.getOldLocation().getWorld() != null) {\n                tpLoc = data.getOldLocation();\n            } else {\n                tpLoc = Bukkit.getWorlds().get(0).getSpawnLocation();\n            }\n            keepInventory = false;\n        }\n\n        if (player.isDead()) {\n            new BukkitRunnable() {\n                @Override\n                public void run() {\n                    player.spigot().respawn();\n                    reset(tpLoc, keepInventory);\n                }\n            }.runTaskLater(plugin, 1L);\n        } else {\n            reset(tpLoc, keepInventory);\n        }\n    }\n\n    @Override\n    public void reset(Location tpLoc, boolean keepInventory) {\n        try {\n            player.teleport(tpLoc);\n            player.setGameMode(data.getOldGameMode());\n            if (!keepInventory) {\n                while (data.getOldInventory().size() > 36) {\n                    data.getOldInventory().remove(36);\n                }\n                player.getInventory().setContents(data.getOldInventory().toArray(new ItemStack[36]));\n                player.getInventory().setArmorContents(data.getOldArmor().toArray(new ItemStack[4]));\n                if (is1_9) {\n                    player.getInventory().setItemInOffHand(data.getOldOffHand());\n                }\n                player.setLevel(data.getOldLevel());\n                player.setExp(data.getOldExp());\n                player.setFoodLevel(data.getOldFoodLevel());\n                player.setFireTicks(data.getOldFireTicks());\n\n                for (PotionEffect effect : player.getActivePotionEffects()) {\n                    player.removePotionEffect(effect.getType());\n                }\n                player.addPotionEffects(data.getOldPotionEffects());\n\n                if (is1_9) {\n                    player.setCollidable(data.getOldCollidabilityState());\n                    player.setInvulnerable(data.getOldInvulnerabilityState());\n                    for (Attribute attribute : Attribute.values()) {\n                        AttributeInstance instance = player.getAttribute(attribute);\n                        if (instance == null) {\n                            continue;\n                        }\n                        instance.setBaseValue((double) data.getOldAttributeBases().get(attribute));\n                        for (AttributeModifier mod : instance.getModifiers()) {\n                            instance.removeModifier(mod);\n                        }\n                        for (/*AttributeModifier*/Object mod : data.getOldAttributeMods().get(attribute)) { // TODO Remove casts when 1.8 is dropped\n                            instance.addModifier((AttributeModifier) mod);\n                        }\n                    }\n                }\n                player.setAllowFlight(data.getOldFlyingState());\n\n            } else {\n                for (ItemStack item : player.getInventory().getContents()) {\n                    if (item == null) {\n                        continue;\n                    }\n                    if (plugin.isDungeonItem(item)) {\n                        item.setAmount(0);\n                    }\n                }\n            }\n\n        } catch (NullPointerException exception) {\n            exception.printStackTrace();\n            player.setHealth(0);\n            MessageUtil.log(plugin, \"&4Killed player &6\" + player.getName() + \"&4 because the data to restore his main inventory is corrupted :(\");\n        }\n\n        data.clearPlayerState();\n    }\n\n    /**\n     * Starts the tutorial\n     */\n    public void startTutorial() {\n        Dungeon dungeon = plugin.getMainConfig().getTutorialDungeon();\n        if (dungeon == null) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_TUTORIAL_DOES_NOT_EXIST.getMessage());\n            return;\n        }\n\n        if (xlib.getPermissionProvider() != null && xlib.getPermissionProvider().hasGroupSupport()) {\n            String startGroup = plugin.getMainConfig().getTutorialStartGroup();\n            if (startGroup == null) {\n                return;\n            }\n            if (xlib.isGroupEnabled(startGroup)) {\n                xlib.getPermissionProvider().playerAddGroup(player, startGroup);\n            }\n        }\n\n        DGroup dGroup = DGroup.create(plugin, Cause.TUTORIAL, player, null, null, dungeon);\n        if (dGroup == null) {\n            return;\n        }\n\n        Game game = new DGame(plugin, dungeon, dGroup);\n        game.setTutorial(true);\n        GameWorld gameWorld = game.ensureWorldIsLoaded(true);\n        new DGamePlayer(plugin, player, gameWorld);\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{player=\" + player + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameGoal;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.event.group.GroupCollectRewardEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupCreateEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupDisbandEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupFinishDungeonEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupFinishFloorEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerJoinEvent;\nimport de.erethon.dungeonsxl.api.event.group.GroupStartFloorEvent;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerCache;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup.Color;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DDungeon;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.dungeon.DungeonConfig;\nimport de.erethon.dungeonsxl.global.GroupSign;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.player.PlayerCollection;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.UUID;\nimport org.bukkit.Bukkit;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitTask;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DGroup implements PlayerGroup {\n\n    private DungeonsXL plugin;\n    private PlayerCache dPlayers;\n\n    private static int counter;\n\n    private int id;\n    private String name;\n    private String untaggedName;\n    private GroupSign groupSign;\n    private Player captain;\n    private PlayerCollection players = new PlayerCollection();\n    private PlayerCollection invitedPlayers = new PlayerCollection();\n    private Dungeon dungeon;\n    private Game game;\n    private List<Reward> rewards = new ArrayList<>();\n    private BukkitTask timeIsRunningTask;\n    private Color color;\n    private int score = 0;\n    private int initialLives = -1;\n    private int lives = -1;\n\n    private DGroup() {\n    }\n\n    public static DGroup create(DungeonsXL plugin, GroupCreateEvent.Cause cause, Player leader, String name, Color color, Dungeon dungeon) {\n        if (name == null) {\n            name = color != null ? color.toString() : \"Group\";\n        }\n\n        DGroup group = new DGroup();\n        group.plugin = plugin;\n        group.dPlayers = plugin.getPlayerCache();\n\n        group.id = counter++;\n        group.untaggedName = name;\n        group.name = name + \"#\" + group.id;\n        group.color = color;\n        group.dungeon = dungeon;\n        group.addMember(leader);\n        group.setLeader(leader);\n\n        GroupCreateEvent event = new GroupCreateEvent(group, plugin.getPlayerCache().get(leader), cause);\n        Bukkit.getPluginManager().callEvent(event);\n        if (!event.isCancelled()) {\n            plugin.getGroupCache().add(group.name, group);\n            return group;\n        }\n        return null;\n    }\n\n    // Getters and setters\n    @Override\n    public int getId() {\n        return id;\n    }\n\n    @Override\n    public String getName() {\n        return getDColor().getChatColor() + name;\n    }\n\n    @Override\n    public String getRawName() {\n        return name;\n    }\n\n    @Override\n    public void setName(String name) {\n        plugin.getGroupCache().remove(this);\n        untaggedName = name;\n        this.name = name + \"#\" + id;\n        plugin.getGroupCache().add(name, this);\n    }\n\n    public String getUntaggedName() {\n        return untaggedName;\n    }\n\n    public GroupSign getGroupSign() {\n        return groupSign;\n    }\n\n    public void setGroupSign(GroupSign groupSign) {\n        this.groupSign = groupSign;\n    }\n\n    @Override\n    public Player getLeader() {\n        return captain;\n    }\n\n    @Override\n    public void setLeader(Player player) {\n        captain = player;\n    }\n\n    @Override\n    public PlayerCollection getMembers() {\n        return players;\n    }\n\n    /**\n     * @return the players as a Set&lt;DGlobalPlayer&gt;\n     */\n    public Set<DGlobalPlayer> getDGlobalPlayers() {\n        Set<DGlobalPlayer> players = new HashSet<>();\n        for (UUID uuid : this.players) {\n            players.add((DGlobalPlayer) dPlayers.get(uuid));\n        }\n        return players;\n    }\n\n    public Set<DGamePlayer> getDGamePlayers() {\n        Set<DGamePlayer> players = new HashSet<>();\n        for (UUID uuid : this.players) {\n            GlobalPlayer dPlayer = dPlayers.get(uuid);\n            if (dPlayer instanceof DGamePlayer) {\n                players.add((DGamePlayer) dPlayer);\n            }\n        }\n        return players;\n    }\n\n    @Override\n    public void addMember(Player player, boolean message) {\n        GroupPlayerJoinEvent event = new GroupPlayerJoinEvent(this, dPlayers.get(player), false);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        if (message) {\n            sendMessage(DMessage.GROUP_PLAYER_JOINED.getMessage(player.getName()));\n            MessageUtil.sendMessage(player, DMessage.PLAYER_JOIN_GROUP.getMessage());\n        }\n        players.add(player.getUniqueId());\n    }\n\n    @Override\n    public void removeMember(Player player, boolean message) {\n        players.remove(player.getUniqueId());\n        plugin.getGlobalProtectionCache().updateGroupSigns(this);\n\n        if (message) {\n            sendMessage(DMessage.PLAYER_LEFT_GROUP.getMessage(player.getName()));\n        }\n\n        if (isEmpty()) {\n            GroupDisbandEvent event = new GroupDisbandEvent(this, dPlayers.get(player), GroupDisbandEvent.Cause.GROUP_IS_EMPTY);\n            Bukkit.getPluginManager().callEvent(event);\n            if (!event.isCancelled()) {\n                delete();\n            }\n        }\n    }\n\n    @Override\n    public PlayerCollection getInvitedPlayers() {\n        return invitedPlayers;\n    }\n\n    @Override\n    public void addInvitedPlayer(Player player, boolean silent) {\n        if (player == null) {\n            return;\n        }\n\n        if (plugin.getPlayerGroup(player) != null) {\n            if (!silent) {\n                MessageUtil.sendMessage(getLeader(), DMessage.ERROR_IN_GROUP.getMessage(player.getName()));\n            }\n            return;\n        }\n\n        if (!silent) {\n            MessageUtil.sendMessage(player, DMessage.PLAYER_INVITED.getMessage(getLeader().getName(), name));\n        }\n\n        // Send message\n        if (!silent) {\n            sendMessage(DMessage.GROUP_INVITED_PLAYER.getMessage(getLeader().getName(), player.getName(), name));\n        }\n\n        // Add player\n        invitedPlayers.add(player.getUniqueId());\n    }\n\n    @Override\n    public void removeInvitedPlayer(Player player, boolean silent) {\n        if (player == null) {\n            return;\n        }\n\n        if (plugin.getPlayerGroup(player) != this) {\n            if (!silent) {\n                MessageUtil.sendMessage(getLeader(), DMessage.ERROR_NOT_IN_GROUP.getMessage(player.getName(), name));\n            }\n            return;\n        }\n\n        if (!silent) {\n            MessageUtil.sendMessage(player, DMessage.PLAYER_UNINVITED.getMessage(player.getName(), name));\n        }\n\n        // Send message\n        if (!silent) {\n            for (Player groupPlayer : players.getOnlinePlayers()) {\n                MessageUtil.sendMessage(groupPlayer, DMessage.GROUP_UNINVITED_PLAYER.getMessage(getLeader().getName(), player.getName(), name));\n            }\n        }\n\n        invitedPlayers.remove(player.getUniqueId());\n    }\n\n    @Override\n    public void clearOfflineInvitedPlayers() {\n        ArrayList<UUID> toRemove = new ArrayList<>();\n        for (UUID uuid : invitedPlayers.getUniqueIds()) {\n            if (Bukkit.getPlayer(uuid) == null) {\n                toRemove.add(uuid);\n            }\n        }\n        invitedPlayers.removeAll(toRemove);\n    }\n\n    @Override\n    public Game getGame() {\n        return game;\n    }\n\n    public void setGame(Game game) {\n        this.game = game;\n    }\n\n    @Override\n    public Dungeon getDungeon() {\n        return game != null ? game.getDungeon() : dungeon;\n    }\n\n    /**\n     * {@link #getDungeon()} ignores this if the group is in a game.\n     *\n     * @param dungeon dungeon to set\n     */\n    public void setDungeon(Dungeon dungeon) {\n        this.dungeon = dungeon;\n    }\n\n    /**\n     * Sets the dungeon.\n     *\n     * @param name the name of the dungeon\n     * @return if the action was successful\n     */\n    public boolean setDungeon(String name) {\n        return (dungeon = plugin.getDungeonRegistry().get(name)) != null;\n    }\n\n    public String getDungeonName() {\n        if (getDungeon() == null) {\n            return null;\n        }\n        return getDungeon().getName();\n    }\n\n    public String getMapName() {\n        return getGameWorld() == null ? null : getGameWorld().getName();\n    }\n\n    @Override\n    public boolean isPlaying() {\n        return game != null;\n    }\n\n    @Override\n    public List<Reward> getRewards() {\n        return rewards;\n    }\n\n    @Override\n    public void addReward(Reward reward) {\n        GroupCollectRewardEvent event = new GroupCollectRewardEvent(this, null, reward);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        rewards.add(reward);\n    }\n\n    @Override\n    public void removeReward(Reward reward) {\n        rewards.remove(reward);\n    }\n\n    public BukkitTask getTimeIsRunningTask() {\n        return timeIsRunningTask;\n    }\n\n    public void setTimeIsRunningTask(BukkitTask task) {\n        this.timeIsRunningTask = task;\n    }\n\n    public boolean isEmpty() {\n        return players.size() == 0;\n    }\n\n    public boolean isCustom() {\n        return !name.matches(\"Group#[0-9]{1,}\");\n    }\n\n    /**\n     * Returns the color that represents this group.\n     *\n     * @return the color that represents this group\n     */\n    public Color getDColor() {\n        if (color != null) {\n            return color;\n        } else {\n            return Color.WHITE;\n        }\n    }\n\n    /**\n     * Sets the color that represents this group.\n     *\n     * @param color the group color to set\n     */\n    public void setDColor(Color color) {\n        this.color = color;\n    }\n\n    @Override\n    public int getScore() {\n        return score;\n    }\n\n    @Override\n    public void setScore(int score) {\n        this.score = score;\n    }\n\n    @Override\n    public int getInitialLives() {\n        return initialLives;\n    }\n\n    @Override\n    public void setInitialLives(int initialLives) {\n        this.initialLives = initialLives;\n    }\n\n    @Override\n    public int getLives() {\n        return lives;\n    }\n\n    @Override\n    public void setLives(int lives) {\n        this.lives = lives;\n    }\n\n    @Override\n    public boolean isFinished() {\n        for (DGamePlayer player : getDGamePlayers()) {\n            if (!player.isFinished()) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /* Actions */\n    public boolean teleport() {\n        if (dungeon == null || dungeon.getMap() == null) {\n            sendMessage(DMessage.ERROR_NO_SUCH_DUNGEON.getMessage());\n            return false;\n        }\n\n        if (game == null) {\n            game = new DGame(plugin, dungeon, this);\n        }\n\n        GameWorld target = game.ensureWorldIsLoaded(false);\n        if (target == null) {\n            sendMessage(DMessage.ERROR_TOO_MANY_INSTANCES.getMessage());\n            return false;\n        }\n\n        for (OfflinePlayer offline : players.getOfflinePlayers()) {\n            if (!offline.isOnline()) {\n                players.remove(offline);\n            }\n            Player player = offline.getPlayer();\n            new DGamePlayer(plugin, player, target);\n        }\n        return true;\n    }\n\n    /**\n     * The group finishs the dungeon.\n     */\n    public void finish() {\n        GroupFinishDungeonEvent groupFinishDungeonEvent = new GroupFinishDungeonEvent(this, dungeon);\n        Bukkit.getPluginManager().callEvent(groupFinishDungeonEvent);\n        if (groupFinishDungeonEvent.isCancelled()) {\n            return;\n        }\n\n        ((DGame) getGame()).resetWaveKills();\n        getDGamePlayers().forEach(p -> p.leave(false));\n    }\n\n    // TODO: Move code to more appropriate classes\n    /**\n     * The group finishs the current floor.\n     *\n     * @param specifiedFloor the name of the next floor\n     */\n    public void finishFloor(DResourceWorld specifiedFloor) {\n        Game game = getGame();\n        DungeonConfig dConfig = ((DDungeon) dungeon).getConfig();\n        int floorsLeft = getDungeon().getFloors().size() - game.getFloorCount(); //floorCount contains start floor, but dungeon floor list doesn't\n        game.removeUnplayedFloor(game.getWorld().getResource(), false);\n        ResourceWorld newFloor = null;\n        GameWorld.Type type = null;\n        if (game.getWorld().getType() == GameWorld.Type.END_FLOOR) {\n            finish();\n            return;\n        } else if (specifiedFloor != null) {\n            newFloor = specifiedFloor;\n            type = GameWorld.Type.DEFAULT;\n        } else if (floorsLeft > 0) {\n            int random = new Random().nextInt(floorsLeft);\n            newFloor = game.getUnplayedFloors().get(random);\n            type = GameWorld.Type.DEFAULT;\n        } else {\n            newFloor = dConfig.getEndFloor();\n            type = GameWorld.Type.END_FLOOR;\n        }\n\n        GroupFinishFloorEvent event = new GroupFinishFloorEvent(this, game.getWorld(), newFloor);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        GameWorld gameWorld = newFloor.instantiateGameWorld(getGame(), true);\n        gameWorld.setType(type);\n        game.setWorld(gameWorld);\n\n        for (DGamePlayer player : getDGamePlayers()) {\n            player.setInstanceWorld(gameWorld);\n            player.setLastCheckpoint(gameWorld.getStartLocation(this));\n            if (player.getWolf() != null) {\n                player.getWolf().teleport(player.getLastCheckpoint());\n            }\n            player.setFinished(false);\n        }\n        game.start();\n    }\n\n    @Override\n    public void delete() {\n        Game game = getGame();\n\n        plugin.getGroupCache().remove(this);\n\n        if (game != null) {\n            game.removeGroup(this);\n        }\n\n        for (UUID uuid : players.getUniqueIds()) {\n            GlobalPlayer member = dPlayers.get(uuid);\n            if (member instanceof InstancePlayer) {\n                ((InstancePlayer) member).leave();\n            }\n        }\n\n        if (timeIsRunningTask != null) {\n            timeIsRunningTask.cancel();\n        }\n\n        plugin.getGlobalProtectionCache().updateGroupSigns(this);\n        plugin.getGroupAdapters().forEach(a -> a.removeReference(this));\n    }\n\n    public boolean checkStartGame(Game game) {\n        for (Player player : getMembers().getOnlinePlayers()) {\n            GamePlayer gamePlayer = plugin.getPlayerCache().getGamePlayer(player);\n            if (gamePlayer == null) {\n                gamePlayer = new DGamePlayer(plugin, player, getGameWorld());\n            }\n\n            if (!gamePlayer.isReady()) {\n                return false;\n            }\n        }\n\n        GroupStartFloorEvent event = new GroupStartFloorEvent(this, getGameWorld());\n        Bukkit.getPluginManager().callEvent(event);\n        return !event.isCancelled();\n    }\n\n    public void startGame(Game game, int index) {\n        if (color == null) {\n            color = plugin.getMainConfig().getGroupColorPriority(index);\n        }\n        plugin.getGlobalProtectionCache().updateGroupSigns(this);\n\n        GameRuleContainer rules = getDungeon().getRules();\n        initialLives = rules.getState(GameRule.INITIAL_GROUP_LIVES);\n        lives = initialLives;\n        GameGoal goal = rules.getState(GameRule.GAME_GOAL);\n        if (goal.getType().hasComponent(GameGoal.TIME_TO_FINISH) && goal.getState(GameGoal.TIME_TO_FINISH) != -1) {\n            timeIsRunningTask = new TimeIsRunningTask(plugin, this, goal.getState(GameGoal.TIME_TO_FINISH)).runTaskTimer(plugin, 20, 20);\n        }\n\n        for (UUID playerId : getMembers()) {\n            GlobalPlayer player = plugin.getPlayerCache().get(playerId);\n            if (!(player instanceof DGamePlayer)) {\n                MessageUtil.debug(plugin, \"[ERROR] Player isn't a DGamePlayer, registry: \" + plugin.getPlayerCache().getAll());\n                return;\n            }\n            ((DGamePlayer) player).startGame();\n        }\n    }\n\n    public void winGame() {\n        String title = DMessage.GROUP_CONGRATS.getMessage();\n        String subtitle = DMessage.GROUP_CONGRATS_SUB.getMessage(getName());\n        for (DGamePlayer player : getDGamePlayers()) {\n            player.leave(false);\n            MessageUtil.sendTitleMessage(player.getPlayer(), title, subtitle, 20, 20, 100);\n        }\n    }\n\n    // This is not used.\n    public boolean checkRequirements() {\n        if (DPermission.hasPermission(getLeader(), DPermission.IGNORE_REQUIREMENTS)) {\n            return true;\n        }\n\n        for (DGamePlayer dPlayer : getDGamePlayers()) {\n            if (!dPlayer.checkRequirements(dungeon)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Sends a message to all players in the group.\n     *\n     * @param message the message to send\n     */\n    public void sendMessage(String message) {\n        for (Player player : players.getOnlinePlayers()) {\n            if (player.isOnline()) {\n                MessageUtil.sendMessage(player, message);\n            }\n        }\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{name=\" + name + \"; captain=\" + captain + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DGroupTag.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport com.gmail.filoghost.holographicdisplays.api.Hologram;\nimport com.gmail.filoghost.holographicdisplays.api.HologramsAPI;\nimport de.erethon.dungeonsxl.DungeonsXL;\n\n/**\n * @author Daniel Saukel\n */\npublic class DGroupTag {\n\n    private DGamePlayer player;\n    private Hologram hologram;\n\n    public DGroupTag(DungeonsXL plugin, DGamePlayer player) {\n        this.player = player;\n        DGroup group = player.getGroup();\n        if (group != null) {\n            hologram = HologramsAPI.createHologram(plugin, player.getPlayer().getLocation().clone().add(0, 3.5, 0));\n            hologram.appendItemLine(group.getDColor().getWoolMaterial().toItemStack());\n            hologram.appendTextLine(group.getName());\n        }\n    }\n\n    public void update() {\n        hologram.teleport(player.getPlayer().getLocation().clone().add(0, 3.5, 0));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DInstancePlayer.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.MainConfig;\nimport de.erethon.dungeonsxl.util.AttributeUtil;\nimport de.erethon.dungeonsxl.util.ParsingUtil;\nimport org.bukkit.World;\nimport org.bukkit.entity.Player;\nimport org.bukkit.potion.PotionEffect;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class DInstancePlayer extends DGlobalPlayer implements InstancePlayer {\n\n    protected MainConfig config;\n\n    private InstanceWorld instanceWorld;\n\n    DInstancePlayer(DungeonsXL plugin, Player player, InstanceWorld world) {\n        super(plugin, player, false);\n\n        config = plugin.getMainConfig();\n\n        instanceWorld = world;\n        getData().savePlayerState(player);\n    }\n\n    /* Getters and setters */\n    @Override\n    public InstanceWorld getInstanceWorld() {\n        return instanceWorld;\n    }\n\n    @Override\n    public World getWorld() {\n        return instanceWorld.getWorld();\n    }\n\n    public void setInstanceWorld(InstanceWorld instanceWorld) {\n        this.instanceWorld = instanceWorld;\n    }\n\n    // Players in dungeons never get announcer messages\n    @Override\n    public boolean isAnnouncerEnabled() {\n        return false;\n    }\n\n    /* Actions */\n    /**\n     * Clear the player's inventory, potion effects etc.\n     * <p>\n     * Does NOT handle flight.\n     */\n    public void clearPlayerData() {\n        player.getInventory().clear();\n        player.getInventory().setArmorContents(null);\n        player.setExp(0f);\n        player.setLevel(0);\n        double maxHealth;\n        if (is1_9) {\n            AttributeUtil.resetPlayerAttributes(player);\n            maxHealth = player.getAttribute(AttributeUtil.MAX_HEALTH).getValue();\n        } else {\n            maxHealth = player.getMaxHealth();\n        }\n        player.setHealth(maxHealth);\n        player.setFoodLevel(20);\n        player.setExp(0f);\n        player.setLevel(0);\n        for (PotionEffect effect : player.getActivePotionEffects()) {\n            player.removePotionEffect(effect.getType());\n        }\n        if (is1_9) {\n            player.setCollidable(true);\n            player.setInvulnerable(false);\n        }\n    }\n\n    /**\n     * Delete this DInstancePlayer. Creates a DGlobalPlayer to replace it!\n     */\n    public void delete() {\n        if (player.isOnline()) {\n            // Create a new DGlobalPlayer (outside a dungeon)\n            new DGlobalPlayer(this);\n\n        } else {\n            plugin.getPlayerCache().remove(this);\n        }\n    }\n\n    /**\n     * Makes the player send a message to the world.\n     *\n     * @param message the message to send\n     */\n    public void chat(String message) {\n        String chatFormat = instanceWorld instanceof GameWorld ? config.getChatFormatGame() : config.getChatFormatEdit();\n        instanceWorld.sendMessage(ParsingUtil.replaceChatPlaceholders(chatFormat, this) + message);\n\n        for (GlobalPlayer player : plugin.getPlayerCache()) {\n            if (player.isInChatSpyMode()) {\n                if (!getWorld().getPlayers().contains(player.getPlayer())) {\n                    player.sendMessage(ParsingUtil.replaceChatPlaceholders(config.getChatFormatSpy(), this) + message);\n                }\n            }\n        }\n    }\n\n    /* Abstracts */\n    /**\n     * Repeating checks for the player.\n     */\n    public abstract void update();\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DPermission.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.permissions.Permission;\nimport org.bukkit.permissions.PermissionDefault;\nimport static org.bukkit.permissions.PermissionDefault.*;\n\n/**\n * @author Daniel Saukel\n */\npublic enum DPermission {\n\n    // Main nodes\n    ANNOUNCE(\"announce\", OP),\n    BED(\"bed\", OP),\n    BREAK(\"break\", OP),\n    BYPASS(\"bypass\", OP),\n    CHAT(\"chat\", TRUE),\n    CHAT_SPY(\"chatspy\", OP),\n    CMD_EDIT(\"cmdedit\", OP),\n    CREATE(\"create\", OP),\n    DELETE(\"delete\", OP),\n    DUNGEON_ITEM(\"dungeonitem\", OP),\n    EDIT(\"edit\", OP),\n    ENDER_CHEST(\"enderchest\", OP),\n    DISPENSER(\"dispenser\", OP),\n    ENTER(\"enter\", OP),\n    ESCAPE(\"escape\", TRUE),\n    GAME(\"game\", TRUE),\n    GROUP(\"group\", OP),\n    GROUP_ADMIN(\"group.admin\", OP, GROUP),\n    HELP(\"help\", TRUE),\n    IGNORE_REQUIREMENTS(\"ignorerequirements\", OP),\n    IMPORT(\"IMPORT\", OP),\n    INVITE(\"invite\", OP),\n    INSECURE(\"insecure\", OP),\n    JOIN(\"join\", TRUE),\n    KICK(\"kick\", OP),\n    LEAVE(\"leave\", TRUE),\n    LIST(\"list\", OP),\n    LIVES(\"lives\", TRUE),\n    MAIN(\"main\", TRUE),\n    MESSAGE(\"msg\", OP),\n    PLAY(\"play\", OP),\n    PORTAL(\"portal\", OP),\n    RELOAD(\"reload\", OP),\n    RENAME(\"rename\", OP),\n    RESOURCE_PACK(\"resourcepack\", OP),\n    REWARDS(\"rewards\", TRUE),\n    SAVE(\"save\", OP),\n    STATUS(\"status\", OP),\n    /**\n     * Allows to open the settings menu.\n     */\n    SETTINGS(\"settings\", TRUE),\n    /**\n     * Allows to modify dungeon settings unless they have an own node.\n     */\n    SETTINGS_EDIT(\"settings.edit\", OP),\n    /**\n     * Allows to modify global settings.\n     */\n    SETTINGS_GLOBAL(\"settings.global\", OP),\n    /**\n     * Allows to modify player settings unless they have an own node.\n     */\n    SETTINGS_PLAYER(\"settings.player\", TRUE),\n    SIGN(\"sign\", OP),\n    TEST(\"test\", OP),\n    UNINVITE(\"uninvite\", OP),\n    // Kits\n    ADMINISTRATOR(\"*\", OP),\n    HALF_EDITOR(\"halfeditor\", OP, ESCAPE, LIST, MESSAGE, SAVE),\n    FULL_EDITOR(\"fulleditor\", OP, HALF_EDITOR, DELETE, EDIT, PLAY, RENAME, SIGN, TEST),\n    HALF_PLAYER(\"halfplayer\", TRUE, CHAT, ESCAPE, GAME, HELP, JOIN, LEAVE, LIVES, MAIN, SETTINGS, SETTINGS_PLAYER),\n    FULL_PLAYER(\"fullplayer\", OP, HALF_PLAYER, GROUP);\n\n    public static final String PREFIX = \"dxl.\";\n\n    private Permission node;\n    private List<DPermission> children = new ArrayList<>();\n\n    DPermission(String node, PermissionDefault isDefault) {\n        this.node = new Permission(PREFIX + node, isDefault);\n    }\n\n    DPermission(String node, PermissionDefault isDefault, DPermission... children) {\n        this(node, isDefault);\n        this.children = Arrays.asList(children);\n        for (DPermission child : children) {\n            child.node.addParent(node, true);\n        }\n    }\n\n    /**\n     * @return the permission node String\n     */\n    public String getNode() {\n        return node.getName();\n    }\n\n    /**\n     * @return if a player has the node by default\n     */\n    public PermissionDefault isDefault() {\n        return node.getDefault();\n    }\n\n    /**\n     * @return if the node has children\n     */\n    public boolean hasChildren() {\n        return !children.isEmpty();\n    }\n\n    /**\n     * @return the child permissions\n     */\n    public List<DPermission> getChildren() {\n        return children;\n    }\n\n    /**\n     * @param node the node String, with or without \"dxl.\"\n     * @return the DPermission value\n     */\n    public static DPermission getByNode(String node) {\n        for (DPermission permission : values()) {\n            if (permission.getNode().equals(node) || permission.node.equals(node)) {\n                return permission;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * @param sender     the CommandSender\n     * @param permission the permission to check\n     * @return if the player has the permission\n     */\n    public static boolean hasPermission(CommandSender sender, DPermission permission) {\n        return sender.hasPermission(permission.getNode());\n    }\n\n    /**\n     * Registers the permissions.\n     */\n    public static void register() {\n        for (DPermission permission : values()) {\n            Bukkit.getPluginManager().addPermission(permission.node);\n        }\n    }\n\n    /**\n     * Unregisters the permissions.\n     */\n    public static void unregister() {\n        for (DPermission permission : values()) {\n            Bukkit.getPluginManager().removePermission(permission.node);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DPlayerData.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport com.google.common.collect.HashMultimap;\nimport com.google.common.collect.Multimap;\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.util.AttributeUtil;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.config.ConfigUtil;\nimport de.erethon.xlib.config.DREConfig;\nimport de.erethon.xlib.util.EnumUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.bukkit.Bukkit;\nimport org.bukkit.GameMode;\nimport org.bukkit.Location;\nimport org.bukkit.attribute.Attribute;\nimport org.bukkit.attribute.AttributeInstance;\nimport org.bukkit.attribute.AttributeModifier;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.PlayerInventory;\nimport org.bukkit.potion.PotionEffect;\n\n/**\n * Represents a player's persistent data.\n *\n * @author Daniel Saukel\n */\npublic class DPlayerData extends DREConfig {\n\n    protected boolean is1_9 = Version.isAtLeast(Version.MC1_9);\n\n    public static final int CONFIG_VERSION = 4;\n\n    public static final String PREFIX_STATE_PERSISTENCE = \"savePlayer.\";\n    public static final String PREFIX_STATS = \"stats.\";\n\n    // State persistence\n    private boolean keepInventoryAfterLogout = true;\n    private Location oldLocation;\n    private List<ItemStack> oldInventory;\n    private List<ItemStack> oldArmor;\n    private ItemStack oldOffHand;\n    private int oldLvl;\n    private float oldExp;\n    private double oldHealth;\n    private int oldFoodLevel;\n    private int oldFireTicks;\n    private GameMode oldGameMode;\n    private Collection<PotionEffect> oldPotionEffects;\n    private Map/*<Attribute, Double>*/ oldAttributeBases;\n    private Multimap/*<Attribute, AttributeModifier>*/ oldAttributeMods;\n    private boolean oldCollidabilityState;\n    private boolean oldFlyingState;\n    private boolean oldInvulnerabilityState;\n\n    // Stats\n    private Map<String, Long> timeLastStarted = new HashMap<>();\n    private Map<String, Long> timeLastFinished = new HashMap<>();\n    private Map<String, Long> timeLastLoot = new HashMap<>();\n    private boolean finishedTutorial;\n\n    public DPlayerData(File file) {\n        super(file, CONFIG_VERSION);\n\n        if (initialize) {\n            initialize();\n        }\n        load();\n    }\n\n    /* Getters and setters */\n    /**\n     * @return if the player was in a game when he left the game\n     */\n    public boolean wasInGame() {\n        return config.contains(PREFIX_STATE_PERSISTENCE);\n    }\n\n    /**\n     * @return if the inventory shall be reset after a logout\n     */\n    public boolean getKeepInventoryAfterLogout() {\n        return keepInventoryAfterLogout;\n    }\n\n    /**\n     * @param keepInventoryAfterLogout set if the inventory shall be reset after a logout\n     */\n    public void setKeepInventoryAfterLogout(boolean keepInventoryAfterLogout) {\n        this.keepInventoryAfterLogout = keepInventoryAfterLogout;\n        config.set(PREFIX_STATE_PERSISTENCE + \"keepInventoryAfterLogout\", keepInventoryAfterLogout);\n        super.save();\n    }\n\n    /**\n     * @return the old location\n     */\n    public Location getOldLocation() {\n        if (oldLocation.getWorld() == null) {\n            return Bukkit.getWorlds().get(0).getSpawnLocation();\n        }\n        return oldLocation;\n    }\n\n    /**\n     * @param location the location to set\n     */\n    public void setOldLocation(Location location) {\n        oldLocation = location;\n    }\n\n    /**\n     * @return the items in the old inventory\n     */\n    public List<ItemStack> getOldInventory() {\n        return oldInventory;\n    }\n\n    /**\n     * @param inventory the inventory to set\n     */\n    public void setOldInventory(List<ItemStack> inventory) {\n        oldInventory = inventory;\n    }\n\n    /**\n     * @return the items in the old armor slots\n     */\n    public List<ItemStack> getOldArmor() {\n        return oldArmor;\n    }\n\n    /**\n     * @param inventory the inventory to set\n     */\n    public void setOldArmor(List<ItemStack> inventory) {\n        oldArmor = inventory;\n    }\n\n    /**\n     * @return the items in the old off-hand slot\n     */\n    public ItemStack getOldOffHand() {\n        return oldOffHand;\n    }\n\n    /**\n     * @param offHand the off hand item to set\n     */\n    public void setOldOffHand(ItemStack offHand) {\n        oldOffHand = offHand;\n    }\n\n    /**\n     * @return the old level\n     */\n    public int getOldLevel() {\n        return oldLvl;\n    }\n\n    /**\n     * @param level the level to set\n     */\n    public void setOldLevel(int level) {\n        oldLvl = level;\n    }\n\n    /**\n     * @return the old exp\n     */\n    public float getOldExp() {\n        return oldExp;\n    }\n\n    /**\n     * @param exp the amount of exp to set\n     */\n    public void setOldExp(float exp) {\n        oldExp = exp;\n    }\n\n    /**\n     * @return the old health\n     */\n    public double getOldHealth() {\n        return oldHealth;\n    }\n\n    /**\n     * @param health the health to set\n     */\n    public void setOldHealth(double health) {\n        oldHealth = health;\n    }\n\n    /**\n     * @return the old food level\n     */\n    public int getOldFoodLevel() {\n        return oldFoodLevel;\n    }\n\n    /**\n     * @param foodLevel the food level to set\n     */\n    public void setOldFoodLevel(int foodLevel) {\n        oldFoodLevel = foodLevel;\n    }\n\n    /**\n     * @return the old fire ticks\n     */\n    public int getOldFireTicks() {\n        return oldFireTicks;\n    }\n\n    /**\n     * @param fireTicks the fire ticks to set\n     */\n    public void setFireTicks(int fireTicks) {\n        oldFireTicks = fireTicks;\n    }\n\n    /**\n     * @return the old GameMode\n     */\n    public GameMode getOldGameMode() {\n        return oldGameMode;\n    }\n\n    /**\n     * @param gameMode the GameMode to set\n     */\n    public void setOldGameMode(GameMode gameMode) {\n        oldGameMode = gameMode;\n    }\n\n    /**\n     * @return the old potion effects\n     */\n    public Collection<PotionEffect> getOldPotionEffects() {\n        return oldPotionEffects;\n    }\n\n    /**\n     * @param potionEffects the potion effects to set\n     */\n    public void setOldPotionEffects(Collection<PotionEffect> potionEffects) {\n        oldPotionEffects = potionEffects;\n    }\n\n    /**\n     * @return the old attribute bases\n     */\n    public Map/*<Attribute, Double>*/ getOldAttributeBases() {\n        return oldAttributeBases;\n    }\n\n    /**\n     * @param attributeBases the old attribute bases to set\n     */\n    public void setOldAttributeBases(Map/*<Attribute, Double>*/ attributeBases) {\n        oldAttributeBases = attributeBases;\n    }\n\n    /**\n     * @return the old attribute modifiers\n     */\n    public Multimap/*<Attribute, AttributeModifier>*/ getOldAttributeMods() {\n        return oldAttributeMods;\n    }\n\n    /**\n     * @param attributeMods the old attribute modifiers to set\n     */\n    public void setOldAttributeMods(Multimap/*<Attribute, AttributeModifier>*/ attributeMods) {\n        oldAttributeMods = attributeMods;\n    }\n\n    /**\n     * @return if the player was collidable\n     */\n    public boolean getOldCollidabilityState() {\n        return oldCollidabilityState;\n    }\n\n    /**\n     * @param collidableState the collidable state to set\n     */\n    public void setOldCollidabilityState(boolean collidableState) {\n        oldCollidabilityState = collidableState;\n    }\n\n    /**\n     * @return if the player was flying\n     */\n    public boolean getOldFlyingState() {\n        return oldFlyingState;\n    }\n\n    /**\n     * @param flyingState the flying state to set\n     */\n    public void setOldFlyingState(boolean flyingState) {\n        oldFlyingState = flyingState;\n    }\n\n    /**\n     * @return if the player was invulnerable\n     */\n    public boolean getOldInvulnerabilityState() {\n        return oldInvulnerabilityState;\n    }\n\n    /**\n     * @param invulnerabilityState the invulnerability state to set\n     */\n    public void setOldInvulnerabilityState(boolean invulnerabilityState) {\n        oldFlyingState = invulnerabilityState;\n    }\n\n    /**\n     * @return a map of the player's started dungeons with dates.\n     */\n    public Map<String, Long> getTimeLastStarted() {\n        return timeLastStarted;\n    }\n\n    /**\n     * @param dungeon the dungeon to check\n     * @return the time when the player started the dungeon for the last time\n     */\n    public long getTimeLastStarted(String dungeon) {\n        Long time = timeLastStarted.get(dungeon.toLowerCase());\n        if (time == null) {\n            return -1;\n        } else {\n            return time;\n        }\n    }\n\n    /**\n     * @param dungeon the started dungeon\n     * @param time    the time when the dungeon was started\n     */\n    public void setTimeLastStarted(String dungeon, long time) {\n        timeLastStarted.put(dungeon.toLowerCase(), time);\n        save();\n    }\n\n    /**\n     * @return a map of the player's finished dungeons with dates.\n     */\n    public Map<String, Long> getTimeLastFinished() {\n        return timeLastFinished;\n    }\n\n    /**\n     * @param dungeon the dungeon to check\n     * @return the time when the player finished the dungeon for the last time\n     */\n    public long getTimeLastFinished(String dungeon) {\n        Long time = timeLastFinished.get(dungeon.toLowerCase());\n        if (time == null) {\n            return -1;\n        } else {\n            return time;\n        }\n    }\n\n    /**\n     * @param dungeon the finished dungeon\n     * @param time    the time when the dungeon was finished\n     */\n    public void setTimeLastFinished(String dungeon, long time) {\n        timeLastFinished.put(dungeon.toLowerCase(), time);\n        save();\n    }\n\n    /**\n     * @param dungeon the dungeon to check\n     * @return the time when the player received loot from the dungeon for the last time\n     */\n    public long getTimeLastLoot(String dungeon) {\n        Long time = timeLastLoot.get(dungeon.toLowerCase());\n        if (time == null) {\n            return -1;\n        } else {\n            return time;\n        }\n    }\n\n    /**\n     * @param dungeon the finished dungeon\n     * @param time    the time when the dungeon was received\n     */\n    public void setTimeLastLoot(String dungeon, long time) {\n        timeLastLoot.put(dungeon.toLowerCase(), time);\n        save();\n    }\n\n    /**\n     * @return if the player has finished the tutorial\n     */\n    public boolean hasFinishedTutorial() {\n        return finishedTutorial;\n    }\n\n    /**\n     * @param finishedTutorial if the player has finished the tutorial\n     */\n    public void setFinishedTutorial(boolean finishedTutorial) {\n        this.finishedTutorial = finishedTutorial;\n        save();\n    }\n\n    /* Actions */\n    /**\n     * @param dungeon the started dungeon\n     */\n    public void logTimeLastStarted(String dungeon) {\n        timeLastStarted.put(dungeon.toLowerCase(), System.currentTimeMillis());\n        save();\n    }\n\n    /**\n     * @param dungeon the finished dungeon\n     */\n    public void logTimeLastFinished(String dungeon) {\n        timeLastFinished.put(dungeon.toLowerCase(), System.currentTimeMillis());\n        save();\n    }\n\n    /**\n     * @param dungeon the finished dungeon\n     */\n    public void logTimeLastLoot(String dungeon) {\n        timeLastLoot.put(dungeon.toLowerCase(), System.currentTimeMillis());\n        save();\n    }\n\n    @Override\n    public void initialize() {\n        if (!config.contains(PREFIX_STATS + \"timeLastStarted\")) {\n            config.createSection(PREFIX_STATS + \"timeLastStarted\");\n        }\n\n        if (!config.contains(PREFIX_STATS + \"timeLastFinished\")) {\n            config.createSection(PREFIX_STATS + \"timeLastFinished\");\n        }\n\n        if (!config.contains(PREFIX_STATS + \"timeLastLoot\")) {\n            config.createSection(PREFIX_STATS + \"timeLastLoot\");\n        }\n\n        if (!config.contains(PREFIX_STATS + \"finishedTutorial\")) {\n            config.set(PREFIX_STATS + \"finishedTutorial\", finishedTutorial);\n        }\n\n        if (!file.exists()) {\n            try {\n                file.createNewFile();\n                MessageUtil.log(DungeonsXL.getInstance(), \"&6A new player data file has been created and saved as \" + file.getName());\n            } catch (IOException exception) {\n            }\n        }\n\n        save();\n    }\n\n    @Override\n    public void load() {\n        if (config.isConfigurationSection(PREFIX_STATS + \"timeLastStarted\")) {\n            for (String key : config.getConfigurationSection(PREFIX_STATS + \"timeLastStarted\").getKeys(false)) {\n                timeLastStarted.put(key, config.getLong(PREFIX_STATS + \"timeLastStarted.\" + key));\n            }\n        }\n\n        if (config.isConfigurationSection(PREFIX_STATS + \"timeLastFinished\")) {\n            for (String key : config.getConfigurationSection(PREFIX_STATS + \"timeLastFinished\").getKeys(false)) {\n                timeLastFinished.put(key, config.getLong(PREFIX_STATS + \"timeLastFinished.\" + key));\n            }\n        }\n\n        if (config.isConfigurationSection(PREFIX_STATS + \"timeLastLoot\")) {\n            for (String key : config.getConfigurationSection(PREFIX_STATS + \"timeLastLoot\").getKeys(false)) {\n                timeLastLoot.put(key, config.getLong(PREFIX_STATS + \"timeLastLoot.\" + key));\n            }\n        }\n\n        finishedTutorial = config.getBoolean(PREFIX_STATS + \"finishedTutorial\", finishedTutorial);\n\n        if (!wasInGame()) {\n            return;\n        }\n\n        keepInventoryAfterLogout = config.getBoolean(PREFIX_STATE_PERSISTENCE + \"keepInventoryAfterLogout\");\n        oldInventory = (List<ItemStack>) config.get(PREFIX_STATE_PERSISTENCE + \"oldInventory\");\n        oldArmor = (List<ItemStack>) config.get(PREFIX_STATE_PERSISTENCE + \"oldArmor\");\n        oldOffHand = (ItemStack) config.get(PREFIX_STATE_PERSISTENCE + \"oldOffHand\");\n\n        oldLvl = config.getInt(PREFIX_STATE_PERSISTENCE + \"oldLvl\");\n        oldExp = config.getInt(PREFIX_STATE_PERSISTENCE + \"oldExp\");\n        oldHealth = config.getDouble(PREFIX_STATE_PERSISTENCE + \"oldHealth\");\n        oldFoodLevel = config.getInt(PREFIX_STATE_PERSISTENCE + \"oldFoodLevel\");\n        oldFireTicks = config.getInt(PREFIX_STATE_PERSISTENCE + \"oldFireTicks\");\n\n        if (EnumUtil.isValidEnum(GameMode.class, config.getString(PREFIX_STATE_PERSISTENCE + \"oldGameMode\"))) {\n            oldGameMode = GameMode.valueOf(config.getString(PREFIX_STATE_PERSISTENCE + \"oldGameMode\"));\n        } else {\n            oldGameMode = GameMode.SURVIVAL;\n        }\n        oldPotionEffects = (Collection<PotionEffect>) config.get(PREFIX_STATE_PERSISTENCE + \"oldPotionEffects\");\n\n        if (is1_9) {\n            Map<String, Object> basesMap = ConfigUtil.getMap(config, PREFIX_STATE_PERSISTENCE + \"oldAttributeBases\", false);\n            if (basesMap != null) {\n                oldAttributeBases = new HashMap<>();\n                for (Entry<String, Object> entry : basesMap.entrySet()) {\n                    if (!(entry.getValue() instanceof Double)) {\n                        continue;\n                    }\n                    Attribute attribute = AttributeUtil.get(entry.getKey());\n                    Double base = (Double) entry.getValue();\n                    oldAttributeBases.put(attribute, base);\n                }\n            }\n\n            Map<String, Object> modsMap = ConfigUtil.getMap(config, PREFIX_STATE_PERSISTENCE + \"oldAttributeMods\", false);\n            if (modsMap != null) {\n                oldAttributeMods = HashMultimap.create();\n                for (Entry<String, Object> entry : modsMap.entrySet()) {\n                    if (!(entry.getValue() instanceof Collection)) {\n                        continue;\n                    }\n                    Attribute attribute = AttributeUtil.get(entry.getKey());\n                    Collection<AttributeModifier> mods = (Collection<AttributeModifier>) entry.getValue();\n                    oldAttributeMods.putAll(attribute, mods);\n                }\n            }\n        }\n\n        try {\n            oldLocation = (Location) config.get(PREFIX_STATE_PERSISTENCE + \"oldLocation\");\n        } catch (IllegalArgumentException exception) {\n            oldLocation = Bukkit.getWorlds().get(0).getSpawnLocation();\n        }\n\n        oldCollidabilityState = config.getBoolean(PREFIX_STATE_PERSISTENCE + \"oldCollidabilityState\", true);\n        oldFlyingState = config.getBoolean(PREFIX_STATE_PERSISTENCE + \"oldFlyingState\", false);\n        oldInvulnerabilityState = config.getBoolean(PREFIX_STATE_PERSISTENCE + \"oldInvulnerabilityState\", false);\n    }\n\n    @Override\n    public void save() {\n        config.set(PREFIX_STATS + \"timeLastStarted\", timeLastStarted);\n        config.set(PREFIX_STATS + \"timeLastFinished\", timeLastFinished);\n        config.set(PREFIX_STATS + \"timeLastLoot\", timeLastLoot);\n        config.set(PREFIX_STATS + \"finishedTutorial\", finishedTutorial);\n        super.save();\n    }\n\n    /**\n     * Saves the player's data to the file.\n     *\n     * @param player the Player to save\n     */\n    public void savePlayerState(Player player) {\n        oldGameMode = player.getGameMode();\n        oldFireTicks = player.getFireTicks();\n        oldFoodLevel = player.getFoodLevel();\n        oldHealth = player.getHealth();\n        oldExp = player.getExp();\n        oldLvl = player.getLevel();\n        PlayerInventory inv = player.getInventory();\n        oldArmor = new ArrayList<>(4);\n        for (int i = 0; i < 4; i++) {\n            ItemStack itemStack = inv.getArmorContents()[i];\n            oldArmor.add(itemStack != null ? itemStack.clone() : null);\n        }\n        oldInventory = new ArrayList<>(36);\n        for (int i = 0; i < 36; i++) {\n            ItemStack itemStack = inv.getContents()[i];\n            oldInventory.add(itemStack != null ? itemStack.clone() : null);\n        }\n        if (is1_9) {\n            oldOffHand = inv.getItemInOffHand().clone();\n        }\n        oldLocation = player.getLocation();\n        oldPotionEffects = player.getActivePotionEffects();\n        if (is1_9) {\n            oldAttributeBases = new HashMap<>();\n            oldAttributeMods = HashMultimap.create();\n            for (Attribute attribute : Attribute.values()) {\n                AttributeInstance instance = player.getAttribute(attribute);\n                if (instance == null) {\n                    continue;\n                }\n                oldAttributeBases.put(attribute, instance.getBaseValue());\n                for (AttributeModifier mod : instance.getModifiers()) {\n                    oldAttributeMods.put(attribute, mod);\n                }\n            }\n            oldCollidabilityState = player.isCollidable();\n            oldInvulnerabilityState = player.isInvulnerable();\n        }\n        oldFlyingState = player.getAllowFlight();\n\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldGameMode\", oldGameMode.toString());\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldFireTicks\", oldFireTicks);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldFoodLevel\", oldFoodLevel);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldHealth\", oldHealth);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldExp\", oldExp);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldLvl\", oldLvl);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldArmor\", oldArmor);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldInventory\", oldInventory);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldOffHand\", oldOffHand);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldLocation\", oldLocation);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldPotionEffects\", oldPotionEffects);\n        if (is1_9) {\n            for (Object object : oldAttributeBases.entrySet()) {\n                Entry<Attribute, Double> entry = (Entry<Attribute, Double>) object;\n                config.set(PREFIX_STATE_PERSISTENCE + \"oldAttributeBases.\" + entry.getKey().name(), entry.getValue());\n            }\n            for (Object object : oldAttributeMods.asMap().entrySet()) {\n                Entry<Attribute, Collection<AttributeModifier>> entry = (Entry<Attribute, Collection<AttributeModifier>>) object;\n                config.set(PREFIX_STATE_PERSISTENCE + \"oldAttributeMods.\" + entry.getKey().name(), entry.getValue());\n            }\n        }\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldCollidabilityState\", oldCollidabilityState);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldFlyingState\", oldFlyingState);\n        config.set(PREFIX_STATE_PERSISTENCE + \"oldInvulnerabilityState\", oldInvulnerabilityState);\n\n        save();\n    }\n\n    /**\n     * Removes the state data from the file\n     */\n    public void clearPlayerState() {\n        oldGameMode = null;\n        oldFireTicks = 0;\n        oldFoodLevel = 0;\n        oldHealth = 0;\n        oldExp = 0;\n        oldLvl = 0;\n        oldArmor = null;\n        oldInventory = null;\n        oldOffHand = null;\n        oldLocation = null;\n        oldPotionEffects = null;\n        oldAttributeBases = null;\n        oldAttributeMods = null;\n        oldCollidabilityState = true;\n        oldFlyingState = false;\n        oldInvulnerabilityState = false;\n\n        if (wasInGame()) {\n            config.set(\"savePlayer\", null);\n        }\n        save();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/DPlayerListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.util.ParsingUtil;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.LockedDoor;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport java.util.ArrayList;\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.entity.Projectile;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.Action;\nimport org.bukkit.event.entity.EntityDamageByEntityEvent;\nimport org.bukkit.event.entity.EntityDamageEvent;\nimport org.bukkit.event.entity.FoodLevelChangeEvent;\nimport org.bukkit.event.entity.PlayerDeathEvent;\nimport org.bukkit.event.player.AsyncPlayerChatEvent;\nimport org.bukkit.event.player.PlayerCommandPreprocessEvent;\nimport org.bukkit.event.player.PlayerDropItemEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.event.player.PlayerJoinEvent;\nimport org.bukkit.event.player.PlayerMoveEvent;\nimport org.bukkit.event.player.PlayerQuitEvent;\nimport org.bukkit.event.player.PlayerRespawnEvent;\nimport org.bukkit.event.player.PlayerTeleportEvent;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.projectiles.ProjectileSource;\n\n/**\n * @author Daniel Saukel, Frank Baumann, Milan Albrecht\n */\npublic class DPlayerListener implements Listener {\n\n    private DungeonsXL plugin;\n\n    public static final String ALL = \"@all \";\n\n    public DPlayerListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    @EventHandler\n    public void onEntityDamage(EntityDamageEvent event) {\n        if (!(event.getEntity() instanceof LivingEntity)) {\n            return;\n        }\n        LivingEntity entity = ((LivingEntity) event.getEntity());\n        if (isCitizensNPC(entity)) {\n            return;\n        }\n\n        World world = event.getEntity().getWorld();\n        GameWorld gameWorld = plugin.getGameWorld(world);\n        if (gameWorld == null) {\n            return;\n        }\n\n        // Deny all Damage in Lobby\n        if (!gameWorld.isPlaying()) {\n            event.setCancelled(true);\n        }\n\n        boolean dead = entity.getHealth() - event.getFinalDamage() <= 0;\n        if (!dead) {\n            return;\n        }\n        if (entity instanceof Player && !gameWorld.getDungeon().getRules().getState(GameRule.DEATH_SCREEN)) {\n            event.setDamage(0);\n            GamePlayer gamePlayer = plugin.getPlayerCache().getGamePlayer((Player) entity);\n            if (gamePlayer == null) {\n                return;\n            }\n            ((DGamePlayer) gamePlayer).onDeath(null);\n        }\n\n        if (plugin.getDungeonMob(entity) != null) {\n            String killer = null;\n\n            if (event instanceof EntityDamageByEntityEvent) {\n                Entity damager = ((EntityDamageByEntityEvent) event).getDamager();\n\n                if (damager instanceof Projectile) {\n                    if (((Projectile) damager).getShooter() instanceof Player) {\n                        damager = (Player) ((Projectile) damager).getShooter();\n                    }\n                }\n\n                if (damager instanceof Player) {\n                    killer = damager.getName();\n                }\n            }\n\n            ((DGame) gameWorld.getGame()).addKill(killer);\n        }\n    }\n\n    @EventHandler\n    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {\n        World world = event.getEntity().getWorld();\n        GameWorld gameWorld = plugin.getGameWorld(world);\n\n        if (gameWorld == null) {\n            return;\n        }\n\n        Game game = gameWorld.getGame();\n\n        if (game == null) {\n            return;\n        }\n\n        if (!game.hasStarted()) {\n            return;\n        }\n\n        boolean pvp = game.getRules().getState(GameRule.PLAYER_VERSUS_PLAYER);\n        boolean friendlyFire = game.getRules().getState(GameRule.FRIENDLY_FIRE);\n\n        Entity attackerEntity = event.getDamager();\n        Entity attackedEntity = event.getEntity();\n\n        if (attackerEntity instanceof Projectile) {\n            ProjectileSource source = ((Projectile) attackerEntity).getShooter();\n            if (source instanceof Entity) {\n                attackerEntity = (Entity) source;\n            }\n        }\n\n        Player attackerPlayer = null;\n        Player attackedPlayer = null;\n\n        PlayerGroup attackerGroup = null;\n        PlayerGroup attackedGroup = null;\n\n        if (!(attackerEntity instanceof LivingEntity) || !(attackedEntity instanceof LivingEntity)) {\n            return;\n        }\n\n        if (attackerEntity instanceof Player && attackedEntity instanceof Player) {\n            attackerPlayer = (Player) attackerEntity;\n            attackedPlayer = (Player) attackedEntity;\n            if (attackedPlayer.hasMetadata(\"NPC\") || attackerPlayer.hasMetadata(\"NPC\")) {\n                return;\n            }\n\n            attackerGroup = plugin.getPlayerGroup(attackerPlayer);\n            attackedGroup = plugin.getPlayerGroup(attackedPlayer);\n\n            if (!pvp) {\n                event.setCancelled(true);\n                return;\n            }\n\n            if (attackerGroup != null && attackedGroup != null) {\n                if (!friendlyFire && attackerGroup.equals(attackedGroup)) {\n                    event.setCancelled(true);\n                    return;\n                }\n            }\n        }\n\n        // Check Dogs\n        if (attackerEntity instanceof Player || attackedEntity instanceof Player) {\n            for (GamePlayer player : plugin.getPlayerCache().getAllGamePlayersIf(p -> p.getGameWorld() == gameWorld)) {\n                if (player.getWolf() != null) {\n                    if (attackerEntity == player.getWolf() || attackedEntity == player.getWolf()) {\n                        event.setCancelled(true);\n                        return;\n                    }\n                }\n            }\n        }\n\n        for (GamePlayer dPlayer : plugin.getPlayerCache().getAllGamePlayersIf(p -> p.getGameWorld() == gameWorld)) {\n            if (dPlayer.getWolf() != null) {\n                if (attackerEntity instanceof Player || attackedEntity instanceof Player) {\n                    if (attackerEntity == dPlayer.getWolf() || attackedEntity == dPlayer.getWolf()) {\n                        event.setCancelled(true);\n                        return;\n                    }\n\n                } else if (attackerEntity == dPlayer.getWolf() || attackedEntity == dPlayer.getWolf()) {\n                    event.setCancelled(false);\n                    return;\n                }\n            }\n        }\n    }\n\n    // Players don't need to eat in lobbies\n    @EventHandler\n    public void onFoodLevelChange(FoodLevelChangeEvent event) {\n        GameWorld gameWorld = plugin.getGameWorld(event.getEntity().getWorld());\n        if (gameWorld != null) {\n            if (!gameWorld.isPlaying() || !gameWorld.getDungeon().getRules().getState(GameRule.FOOD_LEVEL)) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler(priority = EventPriority.HIGH)\n    public void onPlayerChat(AsyncPlayerChatEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n        GlobalPlayer dPlayer = plugin.getPlayerCache().get(player);\n        if (dPlayer == null) {\n            return;\n        }\n        if (!dPlayer.isInGroupChat()) {\n            return;\n        }\n\n        if (dPlayer instanceof DEditPlayer) {\n            event.setCancelled(true);\n            ((DInstancePlayer) dPlayer).chat(event.getMessage());\n            return;\n        }\n\n        PlayerGroup group = plugin.getPlayerGroup(player);\n        if (group == null) {\n            return;\n        }\n\n        boolean game = event.getMessage().startsWith(ALL) && dPlayer instanceof DInstancePlayer;\n        event.setCancelled(true);\n        if (game) {\n            ((DInstancePlayer) dPlayer).chat(event.getMessage().substring(ALL.length()));\n        } else {\n            group.sendMessage(ParsingUtil.replaceChatPlaceholders(plugin.getMainConfig().getChatFormatGroup(), dPlayer) + event.getMessage());\n        }\n    }\n\n    @EventHandler\n    public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n\n        if (DPermission.hasPermission(player, DPermission.BYPASS)) {\n            return;\n        }\n\n        if (!(plugin.getPlayerCache().get(player) instanceof DInstancePlayer)) {\n            return;\n        }\n        InstancePlayer dPlayer = plugin.getPlayerCache().getInstancePlayer(player);\n\n        String command = event.getMessage().toLowerCase();\n        ArrayList<String> commandWhitelist = new ArrayList<>();\n\n        if (dPlayer instanceof DEditPlayer) {\n            if (DPermission.hasPermission(player, DPermission.CMD_EDIT)) {\n                return;\n\n            } else {\n                commandWhitelist.addAll(plugin.getMainConfig().getEditCommandWhitelist());\n            }\n\n        } else {\n            commandWhitelist.addAll(dPlayer.getGroup().getDungeon().getRules().getState(GameRule.GAME_COMMAND_WHITELIST));\n        }\n\n        commandWhitelist.add(\"dungeonsxl\");\n        commandWhitelist.add(\"dungeon\");\n        commandWhitelist.add(\"dxl\");\n\n        event.setCancelled(true);\n\n        for (String whitelistEntry : commandWhitelist) {\n            if (command.equals('/' + whitelistEntry.toLowerCase()) || command.startsWith('/' + whitelistEntry.toLowerCase() + ' ')) {\n                event.setCancelled(false);\n            }\n        }\n\n        if (event.isCancelled()) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_CMD.getMessage());\n        }\n    }\n\n    @EventHandler\n    public void onPlayerDeath(PlayerDeathEvent event) {\n        Player player = event.getEntity();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n        DGamePlayer dPlayer = (DGamePlayer) plugin.getPlayerCache().getGamePlayer(player);\n        if (dPlayer == null) {\n            return;\n        }\n        dPlayer.onDeath(event);\n    }\n\n    @EventHandler(priority = EventPriority.HIGH)\n    public void onPlayerDropItem(PlayerDropItemEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n\n        GlobalPlayer dPlayer = plugin.getPlayerCache().get(player);\n        if (dPlayer == null) {\n            return;\n        }\n\n        if (dPlayer instanceof EditPlayer && !plugin.getMainConfig().getDropItems() && !DPermission.hasPermission(player, DPermission.INSECURE)) {\n            event.setCancelled(true);\n        }\n\n        if (!(dPlayer instanceof DGamePlayer)) {\n            return;\n        }\n\n        DGamePlayer gamePlayer = (DGamePlayer) dPlayer;\n\n        DGroup dGroup = gamePlayer.getGroup();\n        if (dGroup == null) {\n            return;\n        }\n\n        if (!dGroup.isPlaying()) {\n            event.setCancelled(true);\n            return;\n        }\n\n        if (!gamePlayer.isReady()) {\n            event.setCancelled(true);\n            return;\n        }\n\n        for (ExItem item : dGroup.getDungeon().getRules().getState(GameRule.SECURE_OBJECTS)) {\n            if (event.getItemDrop().getItemStack().isSimilar(item.toItemStack())) {\n                event.setCancelled(true);\n                MessageUtil.sendMessage(player, DMessage.ERROR_DROP.getMessage());\n                return;\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerJoin(PlayerJoinEvent event) {\n        Player player = event.getPlayer();\n        if (plugin.checkPlayer(player)) {\n            return;\n        }\n\n        DGlobalPlayer dPlayer = new DGlobalPlayer(plugin, player);\n        if (dPlayer.getData().wasInGame()) {\n            dPlayer.reset(dPlayer.getData().getOldLocation() != null ? dPlayer.getData().getOldLocation() : Bukkit.getWorlds().get(0).getSpawnLocation(),\n                    dPlayer.getData().getKeepInventoryAfterLogout());\n        }\n\n        if (!dPlayer.getData().hasFinishedTutorial() && plugin.getMainConfig().isTutorialActivated()) {\n            if (plugin.getInstanceCache().size() < plugin.getMainConfig().getMaxInstances()) {\n                dPlayer.startTutorial();\n            } else {\n                event.getPlayer().kickPlayer(DMessage.ERROR_TOO_MANY_TUTORIALS.getMessage());\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerMove(PlayerMoveEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(player.getWorld());\n        DGamePlayer gamePlayer = (DGamePlayer) plugin.getPlayerCache().getGamePlayer(player);\n        if (gameWorld != null && gamePlayer != null) {\n            if (gamePlayer.getDGroupTag() != null) {\n                gamePlayer.getDGroupTag().update();\n            }\n            if (gamePlayer.isStealingFlag()) {\n                DGroup group = gamePlayer.getGroup();\n                Location startLocation = gameWorld.getStartLocation(group);\n\n                if (startLocation.distance(player.getLocation()) < 3) {\n                    gamePlayer.captureFlag();\n                }\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerQuit(PlayerQuitEvent event) {\n        Player player = event.getPlayer();\n        GlobalPlayer dPlayer = plugin.getPlayerCache().get(player);\n        PlayerGroup dGroup = dPlayer.getGroup();\n\n        if (!(dPlayer instanceof InstancePlayer)) {\n            if (dGroup != null) {\n                dGroup.removeMember(player);\n            }\n\n        } else if (dPlayer instanceof GamePlayer) {\n            int timeUntilKickOfflinePlayer = dGroup.getDungeon().getRules().getState(GameRule.TIME_UNTIL_KICK_OFFLINE_PLAYER);\n\n            if (timeUntilKickOfflinePlayer == 0) {\n                ((InstancePlayer) dPlayer).leave();\n\n            } else if (timeUntilKickOfflinePlayer > 0) {\n                dGroup.sendMessage(DMessage.PLAYER_OFFLINE.getMessage(dPlayer.getName(), String.valueOf(timeUntilKickOfflinePlayer)), player);\n                ((GamePlayer) dPlayer).setOfflineTimeMillis(System.currentTimeMillis() + timeUntilKickOfflinePlayer * 1000);\n                return;\n\n            } else {\n                dGroup.sendMessage(DMessage.PLAYER_OFFLINE_NEVER.getMessage(dPlayer.getName()), player);\n                return;\n            }\n\n        } else if (dPlayer instanceof InstancePlayer) {\n            ((InstancePlayer) dPlayer).leave();\n        }\n\n        plugin.getPlayerCache().remove(plugin.getPlayerCache().get(player));\n    }\n\n    @EventHandler\n    public void onPlayerRespawn(PlayerRespawnEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n\n        InstancePlayer instancePlayer = plugin.getPlayerCache().getInstancePlayer(player);\n        if (instancePlayer == null) {\n            return;\n        }\n        InstanceWorld instance = instancePlayer.getInstanceWorld();\n        if (instance == null) {\n            return;\n        }\n\n        GamePlayer gamePlayer = null;\n        PlayerGroup group = instancePlayer.getGroup();\n        Location respawn = null;\n        boolean shouldResetInventory = false;\n        if (instancePlayer instanceof GamePlayer) {\n            gamePlayer = ((GamePlayer) instancePlayer);\n            respawn = gamePlayer.getLastCheckpoint();\n            if (respawn == null) {\n                respawn = group.getGameWorld().getStartLocation(group);\n            }\n            shouldResetInventory = gamePlayer.getGameWorld().getDungeon().getRules().getState(GameRule.RESET_CLASS_INVENTORY_ON_RESPAWN);\n            // Don't forget Doge!\n            if (gamePlayer.getWolf() != null) {\n                gamePlayer.getWolf().teleport(respawn);\n            }\n        }\n        if (respawn == null) {\n            respawn = instancePlayer.getInstanceWorld().getLobbyLocation();\n        }\n        if (respawn == null) {\n            respawn = instancePlayer.getInstanceWorld().getWorld().getSpawnLocation();\n        }\n\n        // Because some plugins set another respawn point, DXL teleports a few ticks later.\n        event.setRespawnLocation(respawn);\n        new RespawnTask(player, gamePlayer, respawn, shouldResetInventory).runTaskLater(plugin, 10L);\n    }\n\n    @EventHandler(priority = EventPriority.HIGHEST)\n    public void onPlayerTeleport(PlayerTeleportEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n        GlobalPlayer dPlayer = plugin.getPlayerCache().get(player);\n\n        World toWorld = event.getTo().getWorld();\n\n        if (dPlayer instanceof InstancePlayer && ((InstancePlayer) dPlayer).getWorld() == toWorld) {\n            return;\n        }\n\n        if (plugin.getInstanceWorld(toWorld) != null) {\n            dPlayer.sendMessage(DMessage.ERROR_JOIN_GROUP.getMessage());\n            dPlayer.sendMessage(ChatColor.GOLD + DMessage.CMD_ENTER_HELP.getMessage());\n            event.setCancelled(true);\n        }\n    }\n\n    public static boolean isCitizensNPC(LivingEntity entity) {\n        return entity.hasMetadata(\"NPC\");\n    }\n\n    /* SUBJECT TO CHANGE */\n    @EventHandler\n    public void onPlayerInteract(PlayerInteractEvent event) {\n        Player player = event.getPlayer();\n        if (isCitizensNPC(player)) {\n            return;\n        }\n        Block clickedBlock = event.getClickedBlock();\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(player.getWorld());\n        if (clickedBlock != null) {\n            // Block Enderchests\n            if (gameWorld != null || plugin.getEditWorld(player.getWorld()) != null) {\n                if (event.getAction() != Action.LEFT_CLICK_BLOCK) {\n                    if (VanillaItem.ENDER_CHEST.is(clickedBlock)) {\n                        if (!DPermission.hasPermission(player, DPermission.BYPASS) && !DPermission.hasPermission(player, DPermission.ENDER_CHEST)) {\n                            MessageUtil.sendMessage(player, DMessage.ERROR_ENDERCHEST.getMessage());\n                            event.setCancelled(true);\n                        }\n\n                    } else if (Category.BEDS.containsBlock(clickedBlock)) {\n                        if (!DPermission.hasPermission(player, DPermission.BYPASS) && !DPermission.hasPermission(player, DPermission.BED)) {\n                            MessageUtil.sendMessage(player, DMessage.ERROR_BED.getMessage());\n                            event.setCancelled(true);\n                        }\n                    }\n                }\n            }\n\n            // Block Dispensers\n            if (gameWorld != null) {\n                if (event.getAction() != Action.LEFT_CLICK_BLOCK) {\n                    if (VanillaItem.DISPENSER.is(clickedBlock)) {\n                        if (!DPermission.hasPermission(player, DPermission.BYPASS) && !DPermission.hasPermission(player, DPermission.DISPENSER)) {\n                            MessageUtil.sendMessage(player, DMessage.ERROR_DISPENSER.getMessage());\n                            event.setCancelled(true);\n                        }\n                    }\n                }\n\n                for (LockedDoor door : gameWorld.getLockedDoors()) {\n                    if (clickedBlock.equals(door.getBlock()) || clickedBlock.equals(door.getAttachedBlock())) {\n                        event.setCancelled(true);\n                        return;\n                    }\n                }\n            }\n        }\n\n        // Check Portals\n        if (event.getItem() != null) {\n            ItemStack item = event.getItem();\n\n            // Copy/Paste a Sign and Block-info\n            if (plugin.getEditWorld(player.getWorld()) != null) {\n                if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {\n                    if (VanillaItem.STICK.is(item)) {\n                        DEditPlayer editPlayer = (DEditPlayer) plugin.getPlayerCache().getEditPlayer(player);\n                        if (editPlayer != null) {\n                            editPlayer.poke(clickedBlock);\n                            event.setCancelled(true);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/RespawnTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class RespawnTask extends BukkitRunnable {\n\n    private Player player;\n    private GamePlayer dPlayer;\n    private Location location;\n    private boolean resetClassInventory;\n\n    public RespawnTask(Player player, GamePlayer dPlayer, Location location, boolean resetClassInventory) {\n        this.location = location;\n        this.player = player;\n        this.dPlayer = dPlayer;\n        this.resetClassInventory = resetClassInventory;\n    }\n\n    @Override\n    public void run() {\n        if (!player.isOnline()) {\n            return;\n        }\n        if (player.getWorld() != location.getWorld() || player.getLocation().distance(location) > 2) {\n            player.teleport(location);\n        }\n        if (resetClassInventory) {\n            dPlayer.setPlayerClass(dPlayer.getPlayerClass());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/SecureModeTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class SecureModeTask extends BukkitRunnable {\n\n    private DungeonsXL plugin;\n\n    public SecureModeTask(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    @Override\n    public void run() {\n        for (Player player : Bukkit.getOnlinePlayers()) {\n            GlobalPlayer globalPlayer = plugin.getPlayerCache().get(player);\n            if (globalPlayer == null) {\n                globalPlayer = new DGlobalPlayer(plugin, player);\n            }\n\n            if (!(globalPlayer instanceof InstancePlayer)) {\n                if (player.getWorld().getName().startsWith(\"DXL_Game_\") | player.getWorld().getName().startsWith(\"DXL_Edit_\") && !DPermission.hasPermission(player, DPermission.INSECURE)) {\n                    player.teleport(Bukkit.getWorlds().get(0).getSpawnLocation());\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/TimeIsRunningTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerKickEvent;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class TimeIsRunningTask extends BukkitRunnable {\n\n    private DungeonsXL plugin;\n\n    private PlayerGroup group;\n    private int time;\n    private int timeLeft;\n\n    public TimeIsRunningTask(DungeonsXL plugin, PlayerGroup group, int time) {\n        this.plugin = plugin;\n        this.group = group;\n        this.time = time;\n        this.timeLeft = time;\n    }\n\n    @Override\n    public void run() {\n        timeLeft--;\n\n        String color = ChatColor.GREEN.toString();\n\n        try {\n            color = (double) timeLeft / (double) time > 0.25 ? ChatColor.GREEN.toString() : ChatColor.DARK_RED.toString();\n\n        } catch (ArithmeticException exception) {\n            color = ChatColor.DARK_RED.toString();\n\n        } finally {\n            for (Player player : group.getMembers().getOnlinePlayers()) {\n                MessageUtil.sendActionBarMessage(player, DMessage.PLAYER_TIME_LEFT.getMessage(color, String.valueOf(timeLeft)));\n\n                GamePlayer dPlayer = plugin.getPlayerCache().getGamePlayer(player);\n                if (timeLeft > 0) {\n                    continue;\n                }\n\n                GroupPlayerKickEvent groupPlayerKickEvent = new GroupPlayerKickEvent(group, dPlayer, GroupPlayerKickEvent.Cause.TIME_EXPIRED);\n                Bukkit.getServer().getPluginManager().callEvent(groupPlayerKickEvent);\n\n                if (!groupPlayerKickEvent.isCancelled()) {\n                    MessageUtil.broadcastMessage(DMessage.PLAYER_TIME_KICK.getMessage(player.getName()));\n                    dPlayer.leave();\n                }\n\n                cancel();\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/player/groupadapter/PartiesAdapter.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.player.groupadapter;\n\nimport com.alessiodp.parties.api.Parties;\nimport com.alessiodp.parties.api.events.bukkit.party.BukkitPartiesPartyPreDeleteEvent;\nimport com.alessiodp.parties.api.events.bukkit.party.BukkitPartiesPartyPreRenameEvent;\nimport com.alessiodp.parties.api.events.bukkit.player.BukkitPartiesPlayerPostJoinEvent;\nimport com.alessiodp.parties.api.events.bukkit.player.BukkitPartiesPlayerPostLeaveEvent;\nimport com.alessiodp.parties.api.interfaces.PartiesAPI;\nimport com.alessiodp.parties.api.interfaces.Party;\nimport com.alessiodp.parties.api.interfaces.PartyPlayer;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GroupAdapter;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport org.bukkit.Bukkit;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * This class may be used as a reference for implementations of the GroupAdapter API.\n *\n * @author Daniel Saukel\n */\npublic class PartiesAdapter extends GroupAdapter<Party> implements Listener {\n\n    private PartiesAPI partiesAPI;\n\n    public PartiesAdapter(DungeonsAPI api) {\n        super(api);\n        Bukkit.getPluginManager().registerEvents(this, api);\n        partiesAPI = Parties.getApi();\n    }\n\n    @Override\n    public PlayerGroup createDungeonGroup(Party eGroup) {\n        PlayerGroup dGroup = dxl.createGroup(Bukkit.getPlayer(eGroup.getLeader()), eGroup.getName());\n        eGroup.getOnlineMembers().forEach(p -> dGroup.addMember(Bukkit.getPlayer(p.getPlayerUUID()), false));\n        groups.put(dGroup, eGroup);\n        return dGroup;\n    }\n\n    @Override\n    public Party getExternalGroup(Player member) {\n        PartyPlayer pPlayer = partiesAPI.getPartyPlayer(member.getUniqueId());\n        if (pPlayer == null) {\n            return null;\n        }\n        return partiesAPI.getParty(pPlayer.getPartyName());\n    }\n\n    @Override\n    public int getGroupOnlineSize(Party eGroup) {\n        return eGroup.getOnlineMembers().size();\n    }\n\n    @Override\n    public boolean isExternalGroupMember(Party eGroup, Player player) {\n        if (eGroup == null) {\n            return false;\n        }\n        return eGroup.getMembers().contains(player.getUniqueId());\n    }\n\n    @EventHandler\n    public void onDeletion(BukkitPartiesPartyPreDeleteEvent event) {\n        PlayerGroup dGroup = getDungeonGroup(event.getParty());\n        if (dGroup != null) {\n            groups.remove(dGroup);\n            dGroup.delete();\n        }\n    }\n\n    @EventHandler\n    public void onRename(BukkitPartiesPartyPreRenameEvent event) {\n        PlayerGroup group = getDungeonGroup(event.getParty());\n        if (group == null) {\n            return;\n        }\n        group.setName(event.getNewPartyName());\n    }\n\n    @EventHandler\n    public void onJoin(BukkitPartiesPlayerPostJoinEvent event) {\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                PlayerGroup group = getDungeonGroup(event.getParty());\n                if (group == null || group.isPlaying()) {\n                    return;\n                }\n                group.addMember(getPlayer(event.getPartyPlayer()), false);\n            }\n        }.runTask(dxl);\n    }\n\n    @EventHandler\n    public void onLeave(BukkitPartiesPlayerPostLeaveEvent event) {\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                PlayerGroup group = getDungeonGroup(event.getParty());\n                if (group == null || group.isPlaying()) {\n                    return;\n                }\n                group.removeMember(getPlayer(event.getPartyPlayer()), false);\n            }\n        }.runTask(dxl);\n    }\n\n    private Player getPlayer(PartyPlayer player) {\n        return Bukkit.getPlayer(player.getPlayerUUID());\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/FeeLevelRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.dungeonsxl.player.DPlayerData;\nimport de.erethon.xlib.chat.MessageUtil;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class FeeLevelRequirement implements Requirement {\n\n    private DungeonsAPI api;\n\n    private int fee;\n\n    public FeeLevelRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the fee\n     */\n    public int getFee() {\n        return fee;\n    }\n\n    /**\n     * @param fee the fee to set\n     */\n    public void setFee(int fee) {\n        this.fee = fee;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        fee = config.getInt(\"feeLevel\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        return getRelevantLevel(player) >= fee;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        int level = getRelevantLevel(player);\n        ChatColor color = level >= fee ? ChatColor.GREEN : ChatColor.DARK_RED;\n        return new ComponentBuilder(DMessage.REQUIREMENT_FEE_LEVEL.getMessage() + \": \").color(ChatColor.GOLD)\n                .append(String.valueOf(level)).color(color)\n                .append(\"/\" + fee).color(ChatColor.WHITE)\n                .create();\n    }\n\n    private int getRelevantLevel(Player player) {\n        if (isKeepInventory(player)) {\n            return player.getLevel();\n        }\n\n        DGlobalPlayer dPlayer = (DGlobalPlayer) api.getPlayerCache().get(player);\n        return dPlayer.getData().getOldLevel();\n    }\n\n    @Override\n    public void demand(Player player) {\n        DGamePlayer dPlayer = (DGamePlayer) api.getPlayerCache().getGamePlayer(player);\n        if (dPlayer == null) {\n            return;\n        }\n\n        DPlayerData data = dPlayer.getData();\n        data.setOldLevel(data.getOldLevel() - fee);\n        data.getConfig().set(DPlayerData.PREFIX_STATE_PERSISTENCE + \"oldLvl\", data.getOldLevel());\n        data.save();\n        if (isKeepInventory(player)) {\n            player.setLevel(player.getLevel() - fee);\n        }\n\n        MessageUtil.sendMessage(player, DMessage.REQUIREMENT_FEE.getMessage(fee + \" levels\"));\n    }\n\n    private boolean isKeepInventory(Player player) {\n        Game game = api.getGame(player);\n        if (game != null) {\n            return game.getRules().getState(GameRule.KEEP_INVENTORY_ON_ENTER);\n        }\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return \"FeeLevelRequirement{fee=\" + fee + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/FeeMoneyRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.chat.MessageUtil;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport net.milkbowl.vault.economy.Economy;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class FeeMoneyRequirement implements Requirement {\n\n    private Economy econ;\n\n    private double fee;\n\n    public FeeMoneyRequirement(DungeonsAPI api) {\n        econ = ((DungeonsXL) api).getXLib().getEconomyProvider();\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the fee\n     */\n    public double getFee() {\n        return fee;\n    }\n\n    /**\n     * @param fee the fee to set\n     */\n    public void setFee(double fee) {\n        this.fee = fee;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        fee = config.getDouble(\"feeMoney\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        if (econ == null) {\n            return true;\n        }\n\n        return econ.getBalance(player) >= fee;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        double money = econ.getBalance(player);\n        ChatColor color = money >= fee ? ChatColor.GREEN : ChatColor.DARK_RED;\n        return new ComponentBuilder(DMessage.REQUIREMENT_FEE_MONEY.getMessage() + \": \").color(ChatColor.GOLD)\n                .append(String.valueOf(money)).color(color)\n                .append(\"/\" + fee).color(ChatColor.WHITE)\n                .create();\n    }\n\n    @Override\n    public void demand(Player player) {\n        if (econ == null) {\n            return;\n        }\n\n        econ.withdrawPlayer(player, fee);\n        MessageUtil.sendMessage(player, DMessage.REQUIREMENT_FEE.getMessage(econ.format(fee)));\n    }\n\n    @Override\n    public String toString() {\n        return \"FeeMoneyRequirement{fee=\" + fee + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/FinishedDungeonsRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.dungeonsxl.player.DPlayerData;\nimport de.erethon.xlib.util.NumberUtil;\nimport de.erethon.xlib.util.SimpleDateUtil;\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * The dungeons that need to be finished before this one may be played.\n *\n * @author Daniel Saukel\n */\npublic class FinishedDungeonsRequirement implements Requirement {\n\n    private static final long HOUR_IN_MILLIS = 3600000L;\n\n    private class DungeonAndTime {\n        String dungeon;\n        double time = Double.NaN;\n\n        @Override\n        public String toString() {\n            return dungeon + (!Double.isNaN(time) ? \" \" + DMessage.REQUIREMENT_FINISHED_DUNGEONS_WITHIN_TIME.getMessage(SimpleDateUtil.decimalToSexagesimalTime(time, 2)) : \"\");\n        }\n    }\n\n    private DungeonsAPI api;\n\n    public FinishedDungeonsRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    /*\n     * finishedDungeons: # all of:\n     *   - 7:vdf    # vdf within the last 7 hours\n     *   - sku/test # one of sku and test\n     */\n    private List<List<DungeonAndTime>> dungeons;\n\n    @Override\n    public void setup(ConfigurationSection config) {\n        List<String> entries = config.getStringList(\"finishedDungeons\");\n        dungeons = new ArrayList<>(entries.size());\n        for (String entry : entries) {\n            List<DungeonAndTime> alternatives = new ArrayList<>();\n            for (String string : entry.split(\"/\")) {\n                String[] split = string.split(\":\");\n                DungeonAndTime dat = new DungeonAndTime();\n                if (split.length > 1) {\n                    dat.time = NumberUtil.parseDouble(split[0], Double.NaN);\n                    dat.dungeon = split[1];\n                } else {\n                    dat.dungeon = split[0];\n                }\n                alternatives.add(dat);\n            }\n            dungeons.add(alternatives);\n        }\n    }\n\n    @Override\n    public boolean check(Player player) {\n        DPlayerData data = ((DGlobalPlayer) api.getPlayerCache().get(player)).getData();\n        allOf:\n        for (List<DungeonAndTime> dats : dungeons) {\n            oneOf:\n            for (DungeonAndTime dat : dats) {\n                if (Double.isNaN(dat.time)) {\n                    if (data.getTimeLastFinished(dat.dungeon) != -1) {\n                        continue allOf;\n                    }\n                } else if (data.getTimeLastFinished(dat.dungeon) + dat.time * HOUR_IN_MILLIS >= System.currentTimeMillis()) {\n                    continue allOf;\n                }\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        DPlayerData data = ((DGlobalPlayer) api.getPlayerCache().get(player)).getData();\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_FINISHED_DUNGEONS_NAME.getMessage() + \":\\n\").color(ChatColor.GOLD);\n        boolean firstAnd = true;\n        for (List<DungeonAndTime> dats : dungeons) {\n            // GREEN if the dungeon is finished within the timeframe\n            // WHITE if the dungeon is not finished, but part of a list where to have another one finished is sufficient\n            // RED if no dungeon of the list is finished within the timeframe\n            List<DungeonAndTime> finished = new ArrayList<>();\n            List<DungeonAndTime> notFinished = new ArrayList<>();\n            for (DungeonAndTime dat : dats) {\n                if ((Double.isNaN(dat.time) && data.getTimeLastFinished(dat.dungeon) != -1)\n                        || !Double.isNaN(dat.time) && data.getTimeLastFinished(dat.dungeon) + dat.time * HOUR_IN_MILLIS\n                        >= System.currentTimeMillis()) {\n                    finished.add(dat);\n                } else {\n                    notFinished.add(dat);\n                }\n            }\n            if (!firstAnd) {\n                builder.append(\";\\n\" + DMessage.REQUIREMENT_FINISHED_DUNGEONS_AND.getMessage() + \" \").color(ChatColor.GOLD);\n            } else {\n                firstAnd = false;\n            }\n            boolean firstOr = true;\n            for (DungeonAndTime dat : finished) {\n                if (!firstOr) {\n                    builder.append(\"\\n\" + DMessage.REQUIREMENT_FINISHED_DUNGEONS_OR.getMessage() + \" \").color(ChatColor.GOLD);\n                } else {\n                    firstOr = false;\n                }\n                builder.append(dat.toString()).color(ChatColor.GREEN);\n            }\n            for (DungeonAndTime dat : notFinished) {\n                if (!firstOr) {\n                    builder.append(\"\\n\" + DMessage.REQUIREMENT_FINISHED_DUNGEONS_OR.getMessage() + \" \").color(ChatColor.GOLD);\n                } else {\n                    firstOr = false;\n                }\n                builder.append(dat.toString()).color(finished.isEmpty() ? ChatColor.DARK_RED : ChatColor.WHITE);\n            }\n        }\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/ForbiddenItemsRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.item.ExItem;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class ForbiddenItemsRequirement implements Requirement {\n\n    private XLib xlib;\n\n    private Map<ExItem, Boolean> forbiddenItems = new HashMap<>();\n\n    public ForbiddenItemsRequirement(DungeonsAPI api) {\n        xlib = api.getXLib();\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the forbidden items (key) and if the check is deep (value)\n     */\n    public Map<ExItem, Boolean> getForbiddenItems() {\n        return forbiddenItems;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        for (String entry : config.getStringList(\"forbiddenItems\")) {\n            if (entry == null) {\n                continue;\n            }\n            boolean star = !entry.contains(\"*\");\n            entry = entry.replace(\"*\", \"\");\n            ExItem item = xlib.getExItem(entry);\n            if (item != null) {\n                forbiddenItems.put(item, star);\n            }\n        }\n    }\n\n    @Override\n    public boolean check(Player player) {\n        for (ItemStack item : player.getInventory().getContents()) {\n            if (item == null) {\n                continue;\n            }\n            ExItem exItem = xlib.getExItem(item);\n            for (Entry<ExItem, Boolean> entry : forbiddenItems.entrySet()) {\n                if (entry.getValue()) {\n                    if (exItem.isSubsumableUnder(entry.getKey())) {\n                        return false;\n                    }\n                } else {\n                    if (exItem.equals(entry.getKey())) {\n                        return false;\n                    }\n                }\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_FORBIDDEN_ITEMS.getMessage() + \": \").color(ChatColor.GOLD);\n\n        Set<ExItem> exInventory = new HashSet<>();\n        for (ItemStack item : player.getInventory().getContents()) {\n            if (item != null) {\n                exInventory.add(xlib.getExItem(item));\n            }\n        }\n\n        boolean first = true;\n        for (Entry<ExItem, Boolean> entry : forbiddenItems.entrySet()) {\n            boolean contains = containsItem(exInventory, entry.getKey(), entry.getValue());\n            ChatColor color = contains ? ChatColor.DARK_RED : ChatColor.GREEN;\n            if (!first) {\n                builder.append(\", \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n            }\n            builder.append(entry.getKey().getName()).color(color);\n        }\n\n        return builder.create();\n    }\n\n    private boolean containsItem(Set<ExItem> exInventory, ExItem forbiddenItem, boolean deepCheck) {\n        for (ExItem item : exInventory) {\n            if ((deepCheck && item.isSubsumableUnder(forbiddenItem)) || (!deepCheck && item.equals(forbiddenItem))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n    @Override\n    public String toString() {\n        return \"ForbiddenItemsRequirement{items=\" + forbiddenItems + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/GroupSizeRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class GroupSizeRequirement implements Requirement {\n\n    private DungeonsAPI api;\n\n    private int minimum;\n    private int maximum;\n\n    public GroupSizeRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    /**\n     * @return the group minimum\n     */\n    public int getMinimum() {\n        return minimum;\n    }\n\n    /**\n     * @param minimum the minimal group size to set\n     */\n    public void setMinimum(int minimum) {\n        this.minimum = minimum;\n    }\n\n    /**\n     * @return the group size maximum\n     */\n    public int getMaximum() {\n        return maximum;\n    }\n\n    /**\n     * @param maximum the maximal group size to set\n     */\n    public void setMaximum(int maximum) {\n        this.maximum = maximum;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        minimum = config.getInt(\"groupSize.minimum\");\n        maximum = config.getInt(\"groupSize.maximum\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        PlayerGroup group = api.getPlayerGroup(player);\n        int size = group.getMembers().size();\n        return size >= minimum && size <= maximum;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        int size = api.getPlayerGroup(player).getMembers().size();\n        ChatColor color = size >= minimum && size <= maximum ? ChatColor.GREEN : ChatColor.DARK_RED;\n        return new ComponentBuilder(DMessage.REQUIREMENT_GROUP_SIZE.getMessage() + \": \").color(ChatColor.GOLD)\n                .append(String.valueOf(size)).color(color)\n                .append(\"/\" + minimum + \"-\" + maximum).color(ChatColor.WHITE)\n                .create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n    @Override\n    public String toString() {\n        return \"GroupSizeRequirement{minimum=\" + minimum + \"; maximum=\" + maximum + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/KeyItemsRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.item.ExItem;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class KeyItemsRequirement implements Requirement {\n\n    private XLib xlib;\n\n    private List<ExItem> keyItems;\n\n    public KeyItemsRequirement(DungeonsAPI api) {\n        xlib = api.getXLib();\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the forbidden items\n     */\n    public List<ExItem> getKeyItems() {\n        return keyItems;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        keyItems = xlib.deserializeExItemList(config, \"keyItems\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        List<ExItem> keyItems = new ArrayList<>(this.keyItems);\n        for (ItemStack item : player.getInventory().getContents()) {\n            if (item == null) {\n                continue;\n            }\n            keyItems.remove(xlib.getExItem(item));\n        }\n        return keyItems.isEmpty();\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_KEY_ITEMS.getMessage() + \": \").color(ChatColor.GOLD);\n\n        Set<ExItem> exInventory = new HashSet<>();\n        for (ItemStack item : player.getInventory().getContents()) {\n            if (item != null) {\n                exInventory.add(xlib.getExItem(item));\n            }\n        }\n\n        boolean first = true;\n        for (ExItem key : keyItems) {\n            ChatColor color = exInventory.contains(key) ? ChatColor.GREEN : ChatColor.DARK_RED;\n            if (!first) {\n                builder.append(\", \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n            }\n            builder.append(key.getName()).color(color);\n        }\n\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n    @Override\n    public String toString() {\n        return \"KeyItemsRequirement{items=\" + keyItems + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/PermissionRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class PermissionRequirement implements Requirement {\n\n    private List<String> permissions = new ArrayList<>();\n\n    public PermissionRequirement(DungeonsAPI api) {\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the permission the player must have to play the dungeon\n     */\n    public List<String> getPermissions() {\n        return permissions;\n    }\n\n    /**\n     * @param permissions the permissions to set\n     */\n    public void setPermissions(List<String> permissions) {\n        this.permissions = permissions;\n    }\n\n    /* Actions */\n    @Override\n    public void setup(ConfigurationSection config) {\n        permissions = config.getStringList(\"permission\");\n    }\n\n    @Override\n    public boolean check(Player player) {\n        for (String permission : permissions) {\n            if (!player.hasPermission(permission)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_PERMISSION.getMessage() + \": \").color(ChatColor.GOLD);\n        boolean first = true;\n        for (String node : permissions) {\n            if (!first) {\n                builder.append(\", \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n            }\n            builder.append(node).color(player.hasPermission(node) ? ChatColor.GREEN : ChatColor.DARK_RED);\n        }\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n    @Override\n    public String toString() {\n        return \"PermissionRequirement{permissions=\" + permissions + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/TimeSinceFinishRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.xlib.util.SimpleDateUtil;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * Time in hours when the game may be played again since it has been started the last time.\n *\n * @author Daniel Saukel\n */\npublic class TimeSinceFinishRequirement implements Requirement {\n\n    private static final long HOUR_IN_MILLIS = 3600000L;\n\n    private DungeonsAPI api;\n\n    private double time;\n\n    public TimeSinceFinishRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    @Override\n    public void setup(ConfigurationSection config) {\n        time = config.getDouble(\"timeSinceFinish\", 0.0);\n    }\n\n    @Override\n    public boolean check(Player player) {\n        DGlobalPlayer globalPlayer = (DGlobalPlayer) api.getPlayerCache().get(player);\n        return (globalPlayer.getData().getTimeLastFinished(globalPlayer.getGroup().getDungeon().getName())\n                + time * HOUR_IN_MILLIS) < System.currentTimeMillis();\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_TIME_SINCE_FINISH\n                .getMessage(SimpleDateUtil.decimalToSexagesimalTime(time, 2)) + \":\\n\").color(ChatColor.GOLD);\n\n        DGlobalPlayer globalPlayer = (DGlobalPlayer) api.getPlayerCache().get(player);\n        String dungeonName = globalPlayer.getGroup().getDungeon().getName();\n        long lastTime = globalPlayer.getData().getTimeLastFinished(dungeonName);\n        if (lastTime == -1) {\n            builder.append(DMessage.REQUIREMENT_TIME_SINCE_NEVER.getMessage()).color(ChatColor.GREEN);\n        } else {\n            ChatColor color = lastTime + time * HOUR_IN_MILLIS < System.currentTimeMillis() ? ChatColor.GREEN : ChatColor.DARK_RED;\n            builder.append(SimpleDateUtil.ddMMMMyyyyhhmmss(lastTime)).color(color);\n        }\n\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/TimeSinceStartRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGlobalPlayer;\nimport de.erethon.xlib.util.SimpleDateUtil;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * Time in hours when the game may be played again since it has been started the last time.\n *\n * @author Daniel Saukel\n */\npublic class TimeSinceStartRequirement implements Requirement {\n\n    private static final long HOUR_IN_MILLIS = 3600000L;\n\n    private DungeonsAPI api;\n\n    private double time;\n\n    public TimeSinceStartRequirement(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    @Override\n    public void setup(ConfigurationSection config) {\n        time = config.getDouble(\"timeSinceStart\", 0.0);\n    }\n\n    @Override\n    public boolean check(Player player) {\n        DGlobalPlayer globalPlayer = (DGlobalPlayer) api.getPlayerCache().get(player);\n        return (globalPlayer.getData().getTimeLastStarted(globalPlayer.getGroup().getDungeon().getName())\n                + time * HOUR_IN_MILLIS) < System.currentTimeMillis();\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_TIME_SINCE_START\n                .getMessage(SimpleDateUtil.decimalToSexagesimalTime(time, 2)) + \":\\n\").color(ChatColor.GOLD);\n\n        DGlobalPlayer globalPlayer = (DGlobalPlayer) api.getPlayerCache().get(player);\n        String dungeonName = globalPlayer.getGroup().getDungeon().getName();\n        long lastTime = globalPlayer.getData().getTimeLastStarted(dungeonName);\n        if (lastTime == -1) {\n            builder.append(DMessage.REQUIREMENT_TIME_SINCE_NEVER.getMessage()).color(ChatColor.GREEN);\n        } else {\n            ChatColor color = lastTime + time * HOUR_IN_MILLIS < System.currentTimeMillis() ? ChatColor.GREEN : ChatColor.DARK_RED;\n            builder.append(SimpleDateUtil.ddMMMMyyyyhhmmss(lastTime)).color(color);\n        }\n\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/requirement/TimeframeRequirement.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.requirement;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Requirement;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\nimport net.md_5.bungee.api.ChatColor;\nimport net.md_5.bungee.api.chat.BaseComponent;\nimport net.md_5.bungee.api.chat.ComponentBuilder;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class TimeframeRequirement implements Requirement {\n\n    public enum Weekday {\n        SUNDAY, // *angry German noises*\n        MONDAY,\n        TUESDAY,\n        WEDNESDAY,\n        THURSDAY,\n        FRIDAY,\n        SATURDAY;\n\n        @Override\n        public String toString() {\n            return DMessage.valueOf(\"DAY_OF_WEEK_\" + ordinal()).getMessage();\n        }\n    }\n\n    public static class Timeframe<T> {\n\n        private T start, end;\n\n        public Timeframe(T start, T end) {\n            this.start = start;\n            this.end = end;\n        }\n\n        public T getStart() {\n            return start;\n        }\n\n        public T getEnd() {\n            return end;\n        }\n\n    }\n\n    private List<Timeframe<Weekday>> weekdays = new ArrayList<>();\n    private List<Timeframe<Integer>> daytimes = new ArrayList<>();\n\n    public TimeframeRequirement(DungeonsAPI api) {\n    }\n\n    @Override\n    public void setup(ConfigurationSection config) {\n        List<String> dates = config.getStringList(\"timeframe\");\n        for (String date : dates) {\n            String[] time = date.split(\"-\");\n\n            Weekday firstDay = EnumUtil.getEnumIgnoreCase(Weekday.class, time[0]);\n            if (firstDay != null) {\n                Weekday secondDay = firstDay;\n                if (time.length == 2) {\n                    secondDay = EnumUtil.getEnumIgnoreCase(Weekday.class, time[1]);\n                }\n                if (secondDay.ordinal() >= firstDay.ordinal()) {\n                    weekdays.add(new Timeframe<>(firstDay, secondDay));\n                }\n            }\n\n            if (time.length < 2) {\n                continue;\n            }\n            int firstHour = NumberUtil.parseInt(time[0], 0);\n            int secondHour = NumberUtil.parseInt(time[1], -1);\n            if (secondHour > firstHour) {\n                daytimes.add(new Timeframe<>(firstHour, secondHour));\n            }\n        }\n    }\n\n    @Override\n    public boolean check(Player player) {\n        boolean match = weekdays.isEmpty();\n        for (Timeframe<Weekday> timeframe : weekdays) {\n            if (isInDayTimeframe(timeframe)) {\n                match = true;\n                break;\n            }\n        }\n        if (!match) {\n            return false;\n        }\n\n        match = daytimes.isEmpty();\n        for (Timeframe<Integer> timeframe : daytimes) {\n            if (isInHourTimeframe(timeframe)) {\n                match = true;\n                break;\n            }\n        }\n        return match;\n    }\n\n    private boolean isInDayTimeframe(Timeframe<Weekday> timeframe) {\n        int day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1; // Calendar days start with 1...\n        return day >= timeframe.getStart().ordinal() && day <= timeframe.getEnd().ordinal();\n    }\n\n    private boolean isInHourTimeframe(Timeframe<Integer> timeframe) {\n        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);\n        return hour >= timeframe.getStart() && hour < timeframe.getEnd();\n    }\n\n    @Override\n    public BaseComponent[] getCheckMessage(Player player) {\n        ComponentBuilder builder = new ComponentBuilder(DMessage.REQUIREMENT_TIMEFRAME.getMessage() + \": \").color(ChatColor.GOLD);\n        boolean first = true;\n        for (Timeframe<Weekday> timeframe : weekdays) {\n            ChatColor color = isInDayTimeframe(timeframe) ? ChatColor.GREEN : ChatColor.DARK_RED;\n            if (!first) {\n                builder.append(\" & \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n            }\n            if (timeframe.getStart() != timeframe.getEnd()) {\n                builder.append(timeframe.getStart() + \"-\" + timeframe.getEnd()).color(color);\n            } else {\n                builder.append(timeframe.getStart().toString()).color(color);\n            }\n        }\n\n        first = true;\n        for (Timeframe<Integer> timeframe : daytimes) {\n            ChatColor color = isInHourTimeframe(timeframe) ? ChatColor.GREEN : ChatColor.DARK_RED;\n            if (!first) {\n                builder.append(\" & \").color(ChatColor.WHITE);\n            } else {\n                first = false;\n                if (!weekdays.isEmpty()) {\n                    builder.append(\" | \").color(ChatColor.WHITE);\n                }\n            }\n            builder.append(timeframe.getStart() + \"-\" + timeframe.getEnd()).color(color);\n        }\n\n        return builder.create();\n    }\n\n    @Override\n    public void demand(Player player) {\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/reward/ItemReward.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.reward;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.Reward;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.bukkit.entity.Player;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class ItemReward implements Reward {\n\n    private DungeonsAPI api;\n    \n    private List<ItemStack> items = new ArrayList<>();\n\n    public ItemReward(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the reward items\n     */\n    public List<ItemStack> getItems() {\n        return items;\n    }\n\n    /**\n     * @param items the reward items to set\n     */\n    public void setItems(List<ItemStack> items) {\n        this.items = items;\n    }\n\n    /**\n     * @param items the reward items to add\n     */\n    public void addItems(ItemStack... items) {\n        this.items.addAll(Arrays.asList(items));\n    }\n\n    /**\n     * @param items the reward items to remove\n     */\n    public void removeItems(ItemStack... items) {\n        this.items.addAll(Arrays.asList(items));\n    }\n\n    /* Actions */\n    @Override\n    public void giveTo(Player player) {\n        api.getPlayerCache().get(player).setRewardItems(items);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/reward/LevelReward.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.reward;\n\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class LevelReward implements Reward {\n\n    private int levels;\n\n    /**\n     * @return the levels\n     */\n    public int getLevels() {\n        return levels;\n    }\n\n    /**\n     * @param levels the levels to add\n     */\n    public void addLevels(int levels) {\n        this.levels += levels;\n    }\n\n    /**\n     * @param levels the levels to set\n     */\n    public void setLevels(int levels) {\n        this.levels = levels;\n    }\n\n    @Override\n    public void giveTo(Player player) {\n        if (levels == 0) {\n            return;\n        }\n\n        player.setLevel(player.getLevel() + levels);\n        MessageUtil.sendMessage(player, DMessage.REWARD_GENERAL.getMessage(levels + \" levels\"));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/reward/MoneyReward.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.reward;\n\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.xlib.chat.MessageUtil;\nimport net.milkbowl.vault.economy.Economy;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class MoneyReward implements Reward {\n    \n    private Economy econ;\n\n    private double money;\n\n    public MoneyReward(Economy econ) {\n        this.econ = econ;\n    }\n\n    /**\n     * @return the money\n     */\n    public double getMoney() {\n        return money;\n    }\n\n    /**\n     * @param money the money to add\n     */\n    public void addMoney(double money) {\n        this.money += money;\n    }\n\n    /**\n     * @param money the money to set\n     */\n    public void setMoney(double money) {\n        this.money = money;\n    }\n\n    @Override\n    public void giveTo(Player player) {\n        if (econ == null || money == 0) {\n            return;\n        }\n\n        econ.depositPlayer(player, money);\n        MessageUtil.sendMessage(player, DMessage.REWARD_GENERAL.getMessage(econ.format(money)));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/reward/RewardListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.reward;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.dungeonsxl.util.ContainerAdapter;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.RewardChest;\nimport de.erethon.xlib.gui.PaginatedInventoryGUI;\nimport de.erethon.xlib.gui.component.InventoryButton;\nimport de.erethon.xlib.gui.layout.PaginatedFlowInventoryLayout;\nimport de.erethon.xlib.gui.layout.PaginatedInventoryLayout.PaginationButtonPosition;\nimport de.erethon.xlib.item.VanillaItem;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.inventory.InventoryOpenEvent;\nimport org.bukkit.event.inventory.InventoryType;\nimport org.bukkit.event.player.PlayerMoveEvent;\nimport org.bukkit.inventory.Inventory;\nimport org.bukkit.inventory.InventoryView;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class RewardListener implements Listener {\n\n    private DungeonsXL plugin;\n\n    public RewardListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    /*@EventHandler\n    public void onInventoryClose(InventoryCloseEvent event) {\n        if (!(event.getPlayer() instanceof Player)) {\n            return;\n        }\n        Player player = (Player) event.getPlayer();\n\n        for (DLootInventory inventory : plugin.getDLootInventories()) {\n            if (PageGUI.getByInventory() != inventory.getInventory()) {\n                continue;\n            }\n\n            if (System.currentTimeMillis() - inventory.getTime() <= 500) {\n                continue;\n            }\n\n            for (ItemStack istack : inventory.getInventory().getContents()) {\n                if (istack != null) {\n                    player.getWorld().dropItem(player.getLocation(), istack);\n                }\n            }\n\n            plugin.getDLootInventories().remove(inventory);\n        }\n    }*/\n    @EventHandler\n    public void onInventoryOpen(InventoryOpenEvent event) {\n        if (!(event.getPlayer() instanceof Player)) {\n            return;\n        }\n\n        InventoryView inventory = event.getView();\n\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(event.getPlayer().getWorld());\n\n        if (gameWorld == null) {\n            return;\n        }\n\n        if (!(ContainerAdapter.isValidContainer(inventory.getTopInventory()))) {\n            return;\n        }\n\n        for (RewardChest rewardChest : gameWorld.getRewardChests()) {\n            if (!rewardChest.getBlock().equals(ContainerAdapter.getHolderBlock(inventory.getTopInventory().getHolder()))) {\n                continue;\n            }\n\n            rewardChest.onOpen((Player) event.getPlayer());\n            event.setCancelled(true);\n            break;\n        }\n\n        if (!plugin.getMainConfig().getOpenInventories() && !DPermission.hasPermission(event.getPlayer(), DPermission.INSECURE)) {\n            World world = event.getPlayer().getWorld();\n            if (event.getInventory().getType() != InventoryType.CREATIVE && plugin.getEditWorld(world) != null) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler\n    public void onPlayerMove(PlayerMoveEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        GlobalPlayer dPlayer = plugin.getPlayerCache().get(player);\n        World world = player.getWorld();\n        Location location = player.getLocation();\n        if (plugin.getInstanceWorld(world) != null) {\n            return;\n        }\n        Block block = location.getBlock();\n        if (dPlayer.hasRewardItemsLeft() && !VanillaItem.NETHER_PORTAL.is(block.getRelative(0, 1, 0)) && !VanillaItem.NETHER_PORTAL.is(block.getRelative(0, -1, 0))\n                && !VanillaItem.NETHER_PORTAL.is(block.getRelative(1, 0, 0)) && !VanillaItem.NETHER_PORTAL.is(block.getRelative(-1, 0, 0))\n                && !VanillaItem.NETHER_PORTAL.is(block.getRelative(0, 0, 1)) && !VanillaItem.NETHER_PORTAL.is(block.getRelative(0, 0, -1))) {\n            PaginatedInventoryGUI lootInventory = new PaginatedInventoryGUI(DMessage.PLAYER_TREASURES.getMessage());\n            PaginatedFlowInventoryLayout layout = new PaginatedFlowInventoryLayout(lootInventory, 54, PaginationButtonPosition.BOTTOM);\n            layout.setSwitchButtonLinePlaceholdersEnabled(true);\n            lootInventory.setCloseListener(e -> {\n                for (Inventory inventory : lootInventory.getOpenedInventories()) {\n                    for (int i = 0; i < 45; i++) {\n                        ItemStack item = inventory.getItem(i);\n                        if (item != null) {\n                            world.dropItem(location, item);\n                        }\n                    }\n                }\n            });\n            lootInventory.setLayout(layout);\n            lootInventory.register();\n            for (ItemStack item : dPlayer.getRewardItems()) {\n                if (item != null) {\n                    InventoryButton button = new InventoryButton(item);\n                    button.setStealable(true);\n                    lootInventory.add(button);\n                }\n            }\n            lootInventory.open(player);\n            dPlayer.setRewardItems(null);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/DSignListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.Action;\nimport org.bukkit.event.block.SignChangeEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DSignListener implements Listener {\n\n    private DungeonsAPI api;\n\n    public DSignListener(DungeonsAPI api) {\n        this.api = api;\n    }\n\n    @EventHandler\n    public void onPlayerInteract(PlayerInteractEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        Block clickedBlock = event.getClickedBlock();\n        if (clickedBlock == null) {\n            return;\n        }\n        GamePlayer dPlayer = api.getPlayerCache().getGamePlayer(player);\n        if (dPlayer == null) {\n            return;\n        }\n\n        DGameWorld gameWorld = (DGameWorld) dPlayer.getGameWorld();\n        if (gameWorld == null) {\n            return;\n        }\n\n        InteractTrigger trigger = InteractTrigger.getByBlock(clickedBlock, gameWorld);\n        if (trigger == null) {\n            return;\n        }\n        if (event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_BLOCK) {\n            trigger.trigger(true, player);\n        }\n    }\n\n    @EventHandler\n    public void onSignChange(SignChangeEvent event) {\n        String[] lines = event.getLines();\n        if (lines[0].length() < 3 || !lines[0].startsWith(\"[\")) {\n            return;\n        }\n        Player player = event.getPlayer();\n        Block block = event.getBlock();\n        BlockState state = block.getState();\n        if (!(state instanceof Sign)) {\n            return;\n        }\n\n        Sign sign = (Sign) state;\n        EditWorld editWorld = api.getEditWorld(sign.getWorld());\n        if (editWorld == null) {\n            return;\n        }\n\n        // Override sign plugins color codes etc.\n        sign.setLine(0, lines[0]);\n        sign.setLine(1, lines[1]);\n        sign.setLine(2, lines[2]);\n        sign.setLine(3, lines[3]);\n\n        if (DungeonsXL.LEGACY_SIGNS.containsKey(lines[0].substring(1, lines[0].length() - 1).toUpperCase())) {\n            MessageUtil.sendMessage(player, ChatColor.RED + \"https://erethon.de/resources/dxl-signs/deprecated.gif\");\n            MessageUtil.sendMessage(player, ChatColor.LIGHT_PURPLE + \"https://github.com/DRE2N/DungeonsXL/wiki/Legacy-support#updating\");\n            event.setCancelled(true);\n            event.getBlock().setType(VanillaItem.AIR.getMaterial());\n            return;\n        }\n\n        DungeonSign dsign = editWorld.createDungeonSign(sign, sign.getLines());\n        if (dsign == null) {\n            return;\n        }\n\n        if (!player.hasPermission(dsign.getBuildPermission())) {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_PERMISSIONS.getMessage());\n            return;\n        }\n\n        if (dsign.validate()) {\n            editWorld.registerSign(block);\n            MessageUtil.sendMessage(player, DMessage.PLAYER_SIGN_CREATED.getMessage());\n\n        } else {\n            editWorld.removeDungeonSign(block);\n            MessageUtil.sendMessage(player, DMessage.ERROR_SIGN_WRONG_FORMAT.getMessage());\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/LocationSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.xlib.util.BlockUtil;\nimport org.bukkit.Location;\n\n/**\n * @author Daniel Saukel\n */\npublic interface LocationSign extends DungeonSign {\n\n    @Override\n    default void initialize() {\n        double x = getSign().getX() + 0.5;\n        double y = getSign().getY();\n        double z = getSign().getZ() + 0.5;\n        float yaw = BlockUtil.blockFaceToYaw(DungeonsXL.BLOCK_ADAPTER.getFacing(getSign().getBlock()).getOppositeFace());\n        float pitch = 0;\n        setTargetLocation(new Location(getGameWorld().getWorld(), x, y, z, yaw, pitch));\n    }\n\n    Location getTargetLocation();\n\n    void setTargetLocation(Location location);\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/ActionBarSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class ActionBarSign extends MessageSign {\n\n    public ActionBarSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"ActionBar\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".actionbar\";\n    }\n\n    @Override\n    public void sendMessage(Player player) {\n        MessageUtil.sendActionBarMessage(player, text);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/BossShopSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.black_ixx.bossshop.BossShop;\nimport org.black_ixx.bossshop.core.BSShop;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class BossShopSign extends Button {\n\n    private BossShop bossShop;\n\n    private String shopName;\n\n    public BossShopSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public String getShopName() {\n        return shopName;\n    }\n\n    public void setShopName(String name) {\n        shopName = name;\n    }\n\n    @Override\n    public String getName() {\n        return \"BossShop\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".bossshop\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        if (Bukkit.getPluginManager().isPluginEnabled(\"BossShopPro\")) {\n            bossShop = (BossShop) Bukkit.getPluginManager().getPlugin(\"BossShopPro\");\n        } else {\n            bossShop = (BossShop) Bukkit.getPluginManager().getPlugin(\"BossShop\");\n        }\n        if (bossShop == null) {\n            markAsErroneous(\"BossShop not enabled\");\n            return;\n        } else if (bossShop.getAPI().getShop(getLine(1)) == null) {\n            markAsErroneous(\"No such BossShop\");\n            return;\n        }\n\n        shopName = getLine(1);\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        InteractTrigger.addDefault(api, this, getLine(1), getLine(2));\n    }\n\n    @Override\n    public boolean push(Player player) {\n        openShop(player, shopName);\n        return true;\n    }\n\n    public void openShop(Player player, String shopName) {\n        BSShop shop = bossShop.getAPI().getShop(shopName);\n        if (shop != null) {\n            bossShop.getAPI().openShop(player, shop);\n        } else {\n            MessageUtil.sendMessage(player, DMessage.ERROR_NO_SUCH_SHOP.getMessage(shopName));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/ChatMessageSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class ChatMessageSign extends MessageSign {\n\n    public ChatMessageSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"MSG\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".msg\";\n    }\n\n    @Override\n    public void sendMessage(Player player) {\n        MessageUtil.sendMessage(player, text);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/CheckpointSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class CheckpointSign extends Button {\n\n    private List<GamePlayer> done = new ArrayList<>();\n\n    public CheckpointSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Checkpoint\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".checkpoint\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n    }\n\n    @Override\n    public void push() {\n        for (InstancePlayer instancePlayer : getGameWorld().getPlayers()) {\n            GamePlayer gamePlayer = (GamePlayer) instancePlayer;\n            if (done.contains(gamePlayer)) {\n                continue;\n            }\n            gamePlayer.setLastCheckpoint(getSign().getLocation());\n            gamePlayer.sendMessage(DMessage.PLAYER_CHECKPOINT_REACHED.getMessage());\n        }\n\n        getGameWorld().removeDungeonSign(this);\n    }\n\n    @Override\n    public boolean push(Player player) {\n        GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(player);\n        if (!done.contains(gamePlayer)) {\n            done.add(gamePlayer);\n            gamePlayer.setLastCheckpoint(getSign().getLocation());\n            MessageUtil.sendMessage(player, DMessage.PLAYER_CHECKPOINT_REACHED.getMessage());\n        }\n\n        if (done.size() >= getGameWorld().getPlayers().size()) {\n            getGameWorld().removeDungeonSign(this);\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/ClassesSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerClass;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ClassesSign extends Button {\n\n    private PlayerClass playerClass;\n\n    public ClassesSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n        playerClass = api.getClassRegistry().get(sign.getLine(1));\n    }\n\n    public PlayerClass getPlayerClass() {\n        return playerClass;\n    }\n\n    public void setPlayerClass(PlayerClass playerClass) {\n        this.playerClass = playerClass;\n    }\n\n    @Override\n    public String getName() {\n        return \"Classes\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".classes\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return api.getClassRegistry().get(getLine(1)) != null;\n    }\n\n    @Override\n    public void initialize() {\n        if (playerClass != null) {\n            InteractTrigger.addDefault(api, this, playerClass.getName(), \"\");\n            getGameWorld().setClassesEnabled(true);\n        } else {\n            markAsErroneous(\"No such class\");\n        }\n    }\n\n    @Override\n    public boolean push(Player player) {\n        GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(player);\n        gamePlayer.setPlayerClass(playerClass);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/EndSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.GameGoal;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class EndSign extends Button {\n\n    private ResourceWorld floor;\n\n    public EndSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public ResourceWorld getFloor() {\n        return floor;\n    }\n\n    public void setFloor(ResourceWorld floor) {\n        this.floor = floor;\n    }\n\n    @Override\n    public String getName() {\n        return \"End\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".end\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        GameGoal goal = getGame().getRules().getState(GameRule.GAME_GOAL);\n        if (goal.getType() != GameGoal.Type.END) {\n            setToAir();\n            MessageUtil.log(api, \"&4An end sign in the dungeon \" + getGame().getDungeon().getName() + \" is ignored because the game goal is \" + goal.toString());\n            return;\n        }\n\n        if (!getLine(1).isEmpty()) {\n            floor = api.getMapRegistry().get(getLine(1));\n        }\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        String line1, line2;\n        Dungeon dungeon = getGame().getDungeon();\n        if (dungeon.isMultiFloor() && !getGame().getUnplayedFloors().isEmpty() && getGameWorld().getResource() != dungeon.getEndFloor()) {\n            line1 = DMessage.SIGN_FLOOR_1.getMessage();\n            if (floor == null) {\n                line2 = DMessage.SIGN_FLOOR_2.getMessage();\n            } else {\n                line2 = floor.getName().replace(\"_\", \" \");\n            }\n        } else {\n            line1 = DMessage.SIGN_END.getMessage();\n            line2 = \"\";\n        }\n        InteractTrigger.addDefault(api, this, line1, line2);\n    }\n\n    @Override\n    public boolean push(Player player) {\n        DGamePlayer dPlayer = (DGamePlayer) api.getPlayerCache().getGamePlayer(player);\n        if (dPlayer == null) {\n            return true;\n        }\n\n        // TODO: Group with 2 players, player A finishs, player B leaves\n        if (dPlayer.isFinished()) {\n            return true;\n        }\n\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                dPlayer.finishFloor((DResourceWorld) floor);\n            }\n        }.runTaskLater(api, 1L);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/LeaveSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.event.group.GroupPlayerLeaveEvent;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class LeaveSign extends Button {\n\n    public LeaveSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Leave\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".leave\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n        } else {\n            InteractTrigger.addDefault(api, this, DMessage.SIGN_LEAVE.getMessage(), \"\");\n        }\n    }\n\n    @Override\n    public boolean push(Player player) {\n        GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(player);\n        if (gamePlayer != null) {\n            GroupPlayerLeaveEvent event = new GroupPlayerLeaveEvent(gamePlayer.getGroup(), gamePlayer);\n            Bukkit.getPluginManager().callEvent(event);\n            if (event.isCancelled()) {\n                return false;\n            }\n\n            gamePlayer.leave();\n        }\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/LivesModifierSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class LivesModifierSign extends Button {\n\n    public enum Target {\n        GAME,\n        GROUP,\n        PLAYER,\n    }\n\n    private int lives;\n    private Target target;\n\n    public LivesModifierSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public int getLives() {\n        return lives;\n    }\n\n    public void setLives(int lives) {\n        this.lives = lives;\n    }\n\n    @Override\n    public String getName() {\n        return \"Lives\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".lives\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return NumberUtil.parseInt(getLine(1)) != 0;\n    }\n\n    @Override\n    public void initialize() {\n        lives = NumberUtil.parseInt(getLine(1));\n        if (EnumUtil.isValidEnum(Target.class, getLine(2).toUpperCase())) {\n            target = Target.valueOf(getLine(2).toUpperCase());\n        } else {\n            target = Target.PLAYER;\n        }\n    }\n\n    @Override\n    public boolean push(Player player) {\n        switch (target) {\n            case GAME:\n                for (Player gamePlayer : getGame().getPlayers()) {\n                    GamePlayer dPlayer = api.getPlayerCache().getGamePlayer(player);\n                    if (gamePlayer != null) {\n                        modifyLives(dPlayer);\n                    }\n                }\n                break;\n\n            case GROUP:\n                modifyLives(api.getPlayerGroup(player));\n                break;\n\n            case PLAYER:\n                modifyLives(api.getPlayerCache().getGamePlayer(player));\n        }\n\n        return true;\n    }\n\n    public void modifyLives(GamePlayer dPlayer) {\n        dPlayer.setLives(dPlayer.getLives() + lives);\n        if (lives > 0) {\n            MessageUtil.sendMessage(dPlayer.getPlayer(), DMessage.PLAYER_LIVES_ADDED.getMessage(String.valueOf(lives)));\n\n        } else {\n            MessageUtil.sendMessage(dPlayer.getPlayer(), DMessage.PLAYER_LIVES_REMOVED.getMessage(String.valueOf(-1 * lives)));\n        }\n\n        if (dPlayer.getLives() <= 0) {\n            dPlayer.kill();\n        }\n    }\n\n    public void modifyLives(PlayerGroup group) {\n        group.setLives(group.getLives() + lives);\n        if (lives > 0) {\n            group.sendMessage(DMessage.GROUP_LIVES_ADDED.getMessage(String.valueOf(lives)));\n\n        } else {\n            group.sendMessage(DMessage.GROUP_LIVES_REMOVED.getMessage(String.valueOf(-1 * lives)));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/MessageSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class MessageSign extends Button {\n\n    protected String text = \"UNKNOWN MESSAGE\";\n\n    public MessageSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text = text;\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return !getLine(1).isEmpty();\n    }\n\n    @Override\n    public void initialize() {\n        String text = getGameWorld().getDungeon().getRules().getState(GameRule.MESSAGES).get(NumberUtil.parseInt(getLine(1)));\n        if (text != null) {\n            this.text = text;\n        } else {\n            markAsErroneous(\"Unknown message, ID: \" + getLine(1));\n        }\n    }\n\n    @Override\n    public boolean push(Player player) {\n        sendMessage(player);\n        return true;\n    }\n\n    @Override\n    public void push() {\n        for (Player player : getGameWorld().getWorld().getPlayers()) {\n            sendMessage(player);\n        }\n        getGameWorld().removeDungeonSign(this);\n    }\n\n    public abstract void sendMessage(Player player);\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/ReadySign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.util.NumberUtil;\nimport de.erethon.xlib.util.ProgressBar;\nimport java.util.UUID;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class ReadySign extends Button {\n\n    private double autoStart = -1;\n    private boolean triggered = false;\n    private ProgressBar bar;\n\n    public ReadySign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public double getTimeToAutoStart() {\n        return autoStart;\n    }\n\n    public void setTimeToAutoStart(double time) {\n        autoStart = time;\n    }\n\n    @Override\n    public String getName() {\n        return \"Ready\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".ready\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        ((DGameWorld) getGameWorld()).setReadySign(this);\n        if (!getLine(2).isEmpty()) {\n            autoStart = NumberUtil.parseDouble(getLine(2), -1);\n        }\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        InteractTrigger.addDefault(api, this, DMessage.SIGN_READY.getMessage(), \"\");\n    }\n\n    @Override\n    public void push() {\n        if (getGame() == null) {\n            return;\n        }\n\n        if (bar != null) {\n            bar.cancel();\n        }\n\n        readyAll();\n    }\n\n    @Override\n    public boolean push(Player player) {\n        GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(player);\n        ready(gamePlayer);\n\n        if (!triggered && autoStart >= 0) {\n            triggered = true;\n\n            if (gamePlayer != null && !gamePlayer.getGroup().isPlaying()) {\n                bar = new ProgressBar(getGame().getPlayers(), (int) Math.ceil(autoStart)) {\n                    @Override\n                    public void onFinish() {\n                        push();\n                    }\n                };\n                bar.send(api);\n            }\n        }\n\n        return true;\n    }\n\n    private void readyAll() {\n        for (PlayerGroup group : getGame().getGroups()) {\n            for (UUID memberId : group.getMembers()) {\n                Player player = Bukkit.getPlayer(memberId);\n                if (player != null) {\n                    GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(player);\n                    if (gamePlayer == null) {\n                        gamePlayer = new DGamePlayer((DungeonsXL) api, player, getGameWorld());\n                    }\n                    ready(gamePlayer);\n                } else {\n                    group.getMembers().remove(memberId);\n                }\n            }\n        }\n    }\n\n    private void ready(GamePlayer player) {\n        if (player == null) {\n            return;\n        }\n        boolean wasReady = player.isReady();\n\n        if (!getGameWorld().areClassesEnabled() || player.getPlayerClass() != null) {\n            if (player.ready()) {\n                getGame().start();\n                if (bar != null) {\n                    bar.cancel();\n                }\n            }\n        }\n\n        if (!wasReady) {\n            if (player.isReady()) {\n                player.sendMessage(DMessage.PLAYER_READY.getMessage());\n            } else if (getGameWorld().areClassesEnabled()) {\n                player.sendMessage(DMessage.ERROR_READY.getMessage());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/ResourcePackSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class ResourcePackSign extends Button {\n\n    private String resourcePack;\n\n    public ResourcePackSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public String getResourcePack() {\n        return resourcePack;\n    }\n\n    public void setExternalMob(String resourcePack) {\n        this.resourcePack = resourcePack;\n    }\n\n    @Override\n    public String getName() {\n        return \"ResourcePack\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".resourcepack\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return ((DungeonsXL) api).getMainConfig().getResourcePacks().get(getLine(1)) != null || getLine(1).equalsIgnoreCase(\"reset\");\n    }\n\n    @Override\n    public void initialize() {\n        Object url = null;\n        if (getLine(1).equalsIgnoreCase(\"reset\")) {\n            // Placeholder to reset to default\n            url = \"http://google.com\";\n        } else {\n            url = ((DungeonsXL) api).getMainConfig().getResourcePacks().get(getLine(1));\n        }\n\n        if (url instanceof String) {\n            resourcePack = (String) url;\n\n        } else {\n            markAsErroneous(\"Unknown resourcepack format\");\n            return;\n        }\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        InteractTrigger.addDefault(api, this, DMessage.SIGN_RESOURCE_PACK.getMessage(), getLine(1));\n    }\n\n    @Override\n    public boolean push(Player player) {\n        player.setResourcePack(resourcePack);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/SoundMessageSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.SoundCategory;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class SoundMessageSign extends Button {\n\n    private String sound;\n    private SoundCategory category;\n    private float volume;\n    private float pitch;\n\n    public SoundMessageSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"SoundMSG\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".soundmsg\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        if (getLine(1).isEmpty()) {\n            markAsErroneous(\"1. Line is empty; expected input: sound name\");\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        sound = getLine(1);\n        if (getLine(2).isEmpty()) {\n            return;\n        }\n\n        String[] args = getLine(2).split(\",\");\n        if (args.length >= 1 && args.length != 2 && Version.isAtLeast(Version.MC1_11)) {\n            category = EnumUtil.getEnumIgnoreCase(SoundCategory.class, args[0]);\n            if (category == null) {\n                category = SoundCategory.MASTER;\n            }\n        }\n        if (args.length == 2) {\n            volume = (float) NumberUtil.parseDouble(args[0], 5.0);\n            pitch = (float) NumberUtil.parseDouble(args[1], 1.0);\n        } else if (args.length == 3) {\n            volume = (float) NumberUtil.parseDouble(args[1], 5.0);\n            pitch = (float) NumberUtil.parseDouble(args[2], 1.0);\n        }\n    }\n\n    @Override\n    public void push() {\n        for (Player player : getGameWorld().getWorld().getPlayers()) {\n            playSound(player);\n        }\n        getGameWorld().removeDungeonSign(this);\n    }\n\n    @Override\n    public boolean push(Player player) {\n        playSound(player);\n        return true;\n    }\n\n    private void playSound(Player player) {\n        if (Version.isAtLeast(Version.MC1_11)) {\n            player.playSound(getSign().getLocation(), sound, category, volume, pitch);\n        } else {\n            player.playSound(getSign().getLocation(), sound, volume, pitch);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/TeleportSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.sign.LocationSign;\nimport de.erethon.xlib.util.BlockUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Milan Albrecht, Daniel Saukel\n */\npublic class TeleportSign extends Button implements LocationSign {\n\n    private Location targetLocation;\n\n    public TeleportSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public Location getTargetLocation() {\n        return targetLocation;\n    }\n\n    @Override\n    public void setTargetLocation(Location location) {\n        this.targetLocation = location;\n    }\n\n    @Override\n    public String getName() {\n        return \"Teleport\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".teleport\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        for (int i = 1; i <= 2; i++) {\n            if (!getLine(i).isEmpty()) {\n                if (BlockUtil.lettersToYaw(getLine(i)) == -1) {\n                    String[] loc = getLine(i).split(\",\");\n                    if (loc.length != 3) {\n                        return false;\n                    }\n                }\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        LocationSign.super.initialize();\n        for (int i = 1; i <= 2; i++) {\n            if (getLine(i).isEmpty()) {\n                continue;\n            }\n            Integer yaw = BlockUtil.lettersToYaw(getLine(i));\n            if (yaw != null) {\n                targetLocation.setYaw(yaw);\n            } else {\n                String[] loc = getLine(i).split(\",\");\n                if (loc.length == 3) {\n                    double x = NumberUtil.parseDouble(loc[0]);\n                    double y = NumberUtil.parseDouble(loc[1]);\n                    double z = NumberUtil.parseDouble(loc[2]);\n\n                    // If number is even, add +0.5 to teleport to the middle of the block\n                    if (!loc[0].contains(\".\")) {\n                        x += 0.5;\n                    }\n                    if (!loc[2].contains(\".\")) {\n                        z += 0.5;\n                    }\n\n                    targetLocation.setX(x);\n                    targetLocation.setY(y);\n                    targetLocation.setZ(z);\n                }\n            }\n        }\n    }\n\n    @Override\n    public boolean push(Player player) {\n        if (targetLocation != null) {\n            player.teleport(targetLocation);\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/TitleSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.Map;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class TitleSign extends MessageSign {\n\n    private String title, subtitle;\n    private int fadeIn = 10, stay = 70, fadeOut = 20;\n\n    public TitleSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Title\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".title\";\n    }\n\n    @Override\n    public void initialize() {\n        String[] line1 = getLine(1).split(\",\");\n        Map<Integer, String> messages = getGameWorld().getDungeon().getRules().getState(GameRule.MESSAGES);\n        int id0 = NumberUtil.parseInt(line1[0], -1);\n        title = messages.get(id0);\n        if (title == null) {\n            markAsErroneous(\"Unknown message, ID: \" + getLine(1));\n            return;\n        }\n        if (line1.length > 1) {\n            int id1 = NumberUtil.parseInt(line1[1], -1);\n            subtitle = messages.get(id1);\n            if (subtitle == null) {\n                markAsErroneous(\"Unknown message, ID: \" + getLine(1));\n                return;\n            }\n        }\n        if (subtitle == null) {\n            subtitle = \"\";\n        }\n\n        if (getLine(2).isEmpty()) {\n            return;\n        }\n        String[] line2 = getLine(2).split(\",\");\n        if (line2.length != 3) {\n            return;\n        }\n        fadeIn = NumberUtil.parseInt(line2[0], fadeIn);\n        stay = NumberUtil.parseInt(line2[1], stay);\n        fadeOut = NumberUtil.parseInt(line2[2], fadeOut);\n    }\n\n    @Override\n    public void sendMessage(Player player) {\n        MessageUtil.sendTitleMessage(player, title, subtitle, fadeIn, stay, fadeOut);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/button/WaveSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.button;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Button;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.dungeon.DGame;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class WaveSign extends Button {\n\n    private double mobCountIncreaseRate;\n    private boolean teleport;\n\n    public WaveSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public double getMobCountIncreaseRate() {\n        return mobCountIncreaseRate;\n    }\n\n    public void setMobCountIncreaseRate(double mobCountIncreaseRate) {\n        this.mobCountIncreaseRate = mobCountIncreaseRate;\n    }\n\n    public boolean getTeleport() {\n        return teleport;\n    }\n\n    public void setTeleport(boolean teleport) {\n        this.teleport = teleport;\n    }\n\n    @Override\n    public String getName() {\n        return \"Wave\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".wave\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        if (!getLine(1).isEmpty()) {\n            mobCountIncreaseRate = NumberUtil.parseDouble(getLine(1), 2);\n        }\n\n        if (!getLine(2).isEmpty()) {\n            teleport = getLine(2).equals(\"+\") || getLine(2).equals(\"true\");\n        }\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        InteractTrigger.addDefault(api, this, DMessage.SIGN_WAVE_1.getMessage(), DMessage.SIGN_WAVE_2.getMessage());\n    }\n\n    @Override\n    public void push() {\n        ((DGame) getGame()).finishWave(mobCountIncreaseRate, teleport);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/BedSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.TeamBed;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class BedSign extends Passive {\n\n    private int team;\n\n    public BedSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Bed\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".bed\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return NumberUtil.parseInt(getLine(1), -1) != -1;\n    }\n\n    @Override\n    public void initialize() {\n        this.team = NumberUtil.parseInt(getLine(1));\n        Block block = BlockUtilCompat.getAttachedBlock(getSign().getBlock());\n\n        if (Category.BEDS.containsBlock(block)) {\n            if (getGame().getGroups().size() > team) {\n                ((DGameWorld) getGameWorld()).addGameBlock(new TeamBed(api, block, (DGroup) getGame().getGroups().get(team)));\n            }\n        } else {\n            markAsErroneous(\"No bed attached\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/ChestSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.util.ContainerAdapter;\nimport de.erethon.xlib.loottable.LootTable;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic abstract class ChestSign extends Passive {\n\n    protected Block chest;\n\n    protected ItemStack[] chestContent;\n    protected LootTable lootTable;\n\n    protected ChestSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public ItemStack[] getChestContents() {\n        if (chestContent == null) {\n            checkChest();\n        }\n        return chestContent;\n    }\n\n    public void setChestContents(ItemStack[] items) {\n        chestContent = items;\n    }\n\n    public LootTable getLootTable() {\n        return lootTable;\n    }\n\n    public void setLootTable(LootTable lootTable) {\n        this.lootTable = lootTable;\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    /**\n     * Checks for a chest next to the sign and sets the reward to its contents.\n     */\n    protected void checkChest() {\n        Block sign = getSign().getBlock();\n        for (int i = -1; i <= 1; i++) {\n            Block xRelative = sign.getRelative(i, 0, 0);\n            Block yRelative = sign.getRelative(0, i, 0);\n            Block zRelative = sign.getRelative(0, 0, i);\n\n            if (ContainerAdapter.isValidContainer(xRelative)) {\n                if (chestContent == null) {\n                    chestContent = ContainerAdapter.getBlockInventory(xRelative).getContents();\n                }\n                chest = xRelative;\n\n            } else if (ContainerAdapter.isValidContainer(yRelative)) {\n                if (chestContent == null) {\n                    chestContent = ContainerAdapter.getBlockInventory(yRelative).getContents();\n                }\n                chest = yRelative;\n\n            } else if (ContainerAdapter.isValidContainer(zRelative)) {\n                if (chestContent == null) {\n                    chestContent = ContainerAdapter.getBlockInventory(zRelative).getContents();\n                }\n                chest = zRelative;\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/DungeonChestSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.ContainerAdapter;\nimport de.erethon.xlib.item.VanillaItem;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class DungeonChestSign extends ChestSign {\n\n    public DungeonChestSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"DungeonChest\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".dungeonchest\";\n    }\n\n    @Override\n    public void initialize() {\n        // For consistency with reward chests but also for intuitiveness, both lines should be possible\n        if (!getLine(1).isEmpty()) {\n            lootTable = api.getXLib().getLootTable(getLine(1));\n        }\n        if (!getLine(2).isEmpty()) {\n            lootTable = api.getXLib().getLootTable(getLine(2));\n        }\n\n        checkChest();\n        if (chest != null) {\n            setToAir();\n        } else {\n            getSign().getBlock().setType(VanillaItem.CHEST.getMaterial());\n            chest = getSign().getBlock();\n        }\n\n        List<ItemStack> list = null;\n        if (lootTable != null) {\n            list = lootTable.generateLootList();\n        }\n        if (chestContent != null) {\n            if (list != null) {\n                list.addAll(Arrays.asList(chestContent));\n            } else {\n                list = Arrays.asList(chestContent);\n            }\n        }\n        if (list == null) {\n            return;\n        }\n\n        chestContent = Arrays.copyOfRange(list.toArray(ItemStack[]::new), 0, 26);\n        ContainerAdapter.getBlockInventory(chest).setContents(chestContent);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/FlagSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.TeamFlag;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class FlagSign extends Passive {\n\n    private int team;\n\n    public FlagSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Flag\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".flag\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return NumberUtil.parseInt(getLine(1), -1) != -1;\n    }\n\n    @Override\n    public void initialize() {\n        this.team = NumberUtil.parseInt(getLine(1));\n        if (getGame().getGroups().size() > team) {\n            ((DGameWorld) getGameWorld()).addGameBlock(new TeamFlag(api, getSign().getBlock(), (DGroup) getGame().getGroups().get(team)));\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/HologramSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport com.gmail.filoghost.holographicdisplays.api.Hologram;\nimport com.gmail.filoghost.holographicdisplays.api.HologramsAPI;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class HologramSign extends Passive {\n\n    private Hologram hologram;\n\n    public HologramSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Hologram\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".hologram\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        if (Bukkit.getPluginManager().getPlugin(\"HolographicDisplays\") == null) {\n            markAsErroneous(\"HolographicDisplays not enabled\");\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        String text = getGameWorld().getDungeon().getRules().getState(GameRule.MESSAGES).get(NumberUtil.parseInt(getLine(1)));\n        if (text == null) {\n            markAsErroneous(\"Unknown message, ID: \" + getLine(1));\n            return;\n        }\n        String[] holoLines = text.split(\"(?i)<br>\");\n        Location location = getSign().getLocation();\n        location = location.add(0.5, NumberUtil.parseDouble(getLine(2), 2.0), 0.5);\n\n        hologram = HologramsAPI.createHologram(api, location);\n        for (String line : holoLines) {\n            if (line.startsWith(\"Item:\")) {\n                String id = line.replace(\"Item:\", \"\");\n                ItemStack item = null;\n\n                ExItem exItem = api.getXLib().getExItem(id);\n                if (exItem != null) {\n                    item = exItem.toItemStack();\n                }\n\n                hologram.appendItemLine(item);\n\n            } else {\n                hologram.appendTextLine(ChatColor.translateAlternateColorCodes('&', line));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/InteractSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Sign;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Milan Albrecht, Daniel Saukel\n */\npublic class InteractSign extends Passive {\n\n    private int id = 0;\n\n    public InteractSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Interact\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".interact\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean isTriggerLineDisabled() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        Set<Integer> used = new HashSet<>();\n        for (DungeonSign dSign : getEditWorld().getDungeonSigns()) {\n            if (dSign instanceof InteractSign) {\n                used.add(((InteractSign) dSign).id);\n            }\n        }\n\n        if (getLine(1).isEmpty()) {\n            if (!used.isEmpty()) {\n                while (used.contains(id)) {\n                    id++;\n                }\n            }\n\n        } else {\n            id = NumberUtil.parseInt(getLine(1));\n            return !(id == 0 || used.contains(id));\n        }\n\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                getSign().setLine(1, String.valueOf(id));\n                getSign().update(true);\n            }\n        }.runTaskLater(api, 1L);\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        getGameWorld().createTrigger(this, LogicalExpression.parse(\"I\" + id));\n\n        getSign().setLine(0, ChatColor.DARK_BLUE + \"############\");\n        getSign().setLine(1, ChatColor.GREEN + getLine(2));\n        getSign().setLine(2, ChatColor.GREEN + getLine(3));\n        getSign().setLine(3, ChatColor.DARK_BLUE + \"############\");\n        getSign().update();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/LobbySign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.sign.LocationSign;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class LobbySign extends Passive implements LocationSign {\n\n    private Location targetLocation;\n\n    public LobbySign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public Location getTargetLocation() {\n        return targetLocation;\n    }\n\n    @Override\n    public void setTargetLocation(Location location) {\n        this.targetLocation = location;\n    }\n\n    @Override\n    public String getName() {\n        return \"Lobby\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".lobby\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        LocationSign.super.initialize();\n        getGameWorld().setLobbyLocation(targetLocation);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/NoteSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class NoteSign extends Passive {\n\n    public NoteSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Interact\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".note\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/PlaceSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.PlaceableBlock;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class PlaceSign extends Passive {\n\n    public PlaceSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Place\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".place\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        ((DGameWorld) getGameWorld()).addGameBlock(new PlaceableBlock(api, (DGameWorld) getGameWorld(), getSign().getBlock(), getLine(1), getLine(2)));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/ProtectionSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.ProtectedBlock;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class ProtectionSign extends Passive {\n\n    public ProtectionSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Protection\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".protection\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        ((DGameWorld) getGameWorld()).addGameBlock(new ProtectedBlock(api, BlockUtilCompat.getAttachedBlock(getSign().getBlock())));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/RewardChestSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.RewardChest;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Daniel Saukel\n */\npublic class RewardChestSign extends ChestSign {\n\n    private double moneyReward;\n    private int levelReward;\n\n    public RewardChestSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public double getMoneyReward() {\n        return moneyReward;\n    }\n\n    public void setMoneyReward(double amount) {\n        moneyReward = amount;\n    }\n\n    public int getLevelReward() {\n        return levelReward;\n    }\n\n    public void setLevelReward(int amount) {\n        levelReward = amount;\n    }\n\n    @Override\n    public String getName() {\n        return \"RewardChest\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".rewardchest\";\n    }\n\n    @Override\n    public void initialize() {\n        if (!getLine(1).isEmpty()) {\n            String[] attributes = getLine(1).split(\",\");\n            if (attributes.length >= 1) {\n                moneyReward = NumberUtil.parseDouble(attributes[0]);\n            }\n            if (attributes.length >= 2) {\n                levelReward = NumberUtil.parseInt(attributes[1]);\n            }\n        }\n\n        if (!getLine(2).isEmpty()) {\n            lootTable = api.getXLib().getLootTable(getLine(2));\n        }\n\n        checkChest();\n        if (chest != null) {\n            setToAir();\n        } else {\n            getSign().getBlock().setType(VanillaItem.CHEST.getMaterial());\n            chest = getSign().getBlock();\n        }\n\n        List<ItemStack> list = null;\n        if (lootTable != null) {\n            list = lootTable.generateLootList();\n        }\n        if (chestContent != null) {\n            if (list != null) {\n                list.addAll(Arrays.asList(chestContent));\n            } else {\n                list = Arrays.asList(chestContent);\n            }\n        }\n        if (list == null) {\n            return;\n        }\n\n        ((DGameWorld) getGameWorld()).addGameBlock(new RewardChest((DungeonsXL) api, chest, moneyReward, levelReward, list.toArray(ItemStack[]::new)));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/ScriptSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class ScriptSign extends Passive {\n\n    private String scriptName;\n\n    public ScriptSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n        scriptName = lines[1];\n    }\n\n    public String getScriptName() {\n        return scriptName;\n    }\n\n    public void setScriptName(String name) {\n        scriptName = name;\n    }\n\n    @Override\n    public String getName() {\n        return \"Script\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".script\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return ((DungeonsXL) api).getSignScriptRegistry().get(scriptName) != null;\n    }\n\n    @Override\n    public void initialize() {\n        SignScript script = ((DungeonsXL) api).getSignScriptRegistry().get(scriptName);\n        if (script == null) {\n            markAsErroneous(\"The script \\\"\" + scriptName + \"\\\" could not be found.\");\n            return;\n        }\n\n        DungeonSign dSign = null;\n        for (String[] lines : script.getSigns()) {\n            dSign = getGameWorld().createDungeonSign(getSign(), lines);\n            if (dSign.isErroneous()) {\n                getGameWorld().removeDungeonSign(dSign);\n                continue;\n            }\n\n            try {\n                dSign.initialize();\n            } catch (Exception exception) {\n                dSign.markAsErroneous(\"An error occurred while initializing a sign of the type \" + dSign.getName()\n                        + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n                exception.printStackTrace();\n            }\n            if (!dSign.hasTriggers()) {\n                try {\n                    dSign.trigger(null);\n                } catch (Exception exception) {\n                    markAsErroneous(\"An error occurred while triggering a sign of the type \" + getName()\n                            + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n                    exception.printStackTrace();\n                }\n            }\n        }\n\n        if (dSign == null) {\n            markAsErroneous(\"The script \\\"\" + scriptName + \"\\\" could not be found.\");\n            return;\n        }\n        if (dSign.isSetToAir()) {\n            dSign.setToAir();\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/SignScript.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.xlib.chat.MessageUtil;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.configuration.file.YamlConfiguration;\n\n/**\n * Representation of a sign script. Sign scripts allow to merge multiple dungeon signs at one position.\n *\n * @author Daniel Saukel\n */\npublic class SignScript {\n\n    private String name;\n\n    private List<String[]> signs;\n\n    /**\n     * @param file the script file\n     */\n    public SignScript(File file) {\n        this(file.getName().substring(0, file.getName().length() - 4), YamlConfiguration.loadConfiguration(file));\n    }\n\n    /**\n     * @param name   the name of the Announcer\n     * @param config the config that stores the information\n     */\n    public SignScript(String name, FileConfiguration config) {\n        this.name = name;\n        signs = new ArrayList<>(config.getKeys(false).size());\n\n        int i = 0;\n        for (String key : config.getKeys(false)) {\n            List<String> lines = config.getStringList(key);\n            if (lines.size() != 4) {\n                MessageUtil.log(\"Found an invalid sign (ID: \" + key + \") in script \\\"\" + name + \"\\\". Every sign must have 4 text lines.\");\n                continue;\n            }\n            signs.add(i, lines.toArray(new String[4]));\n            i++;\n        }\n    }\n\n    /**\n     * @return the name of the announcer\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @return the signs\n     */\n    public List<String[]> getSigns() {\n        return signs;\n    }\n\n    /**\n     * @param index the index number\n     * @return the lines of the sign\n     */\n    public String[] getLines(int index) {\n        return signs.get(index);\n    }\n\n    /**\n     * @param index the index number\n     * @param lines the lines to set\n     */\n    public void setLines(int index, String[] lines) {\n        signs.set(index, lines);\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{name=\" + name + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/passive/StartSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.passive;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Passive;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.sign.LocationSign;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class StartSign extends Passive implements LocationSign {\n\n    private Location targetLocation;\n    private int id;\n\n    public StartSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    @Override\n    public Location getTargetLocation() {\n        return targetLocation;\n    }\n\n    @Override\n    public void setTargetLocation(Location location) {\n        this.targetLocation = location;\n    }\n\n    @Override\n    public String getName() {\n        return \"Start\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".start\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        LocationSign.super.initialize();\n        id = NumberUtil.parseInt(getLine(1));\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/rocker/BlockSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.rocker;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Rocker;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.compatibility.RuntimeTrait;\nimport de.erethon.xlib.compatibility.RuntimeType;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Milan Albrecht, Daniel Saukel\n */\npublic class BlockSign extends Rocker {\n\n    private ExItem offBlock = VanillaItem.AIR;\n    private ExItem onBlock = VanillaItem.AIR;\n    private byte offBlockData = Byte.MIN_VALUE;\n    private byte onBlockData = Byte.MIN_VALUE;\n\n    public BlockSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Block\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".block\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return true;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return false;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        if (getLine(1).isEmpty()) {\n            offBlock = VanillaItem.AIR;\n\n        } else {\n            String[] line1 = getLine(1).split(\",\");\n            offBlock = api.getXLib().getExItem(line1[0]);\n            if (offBlock == null) {\n                markAsErroneous(\"Could not recognize offBlock, input: \" + getLine(1));\n                return;\n            }\n            if (line1.length > 1) {\n                offBlockData = (byte) NumberUtil.parseInt(line1[1]);\n            }\n        }\n\n        if (getLine(2).isEmpty()) {\n            onBlock = VanillaItem.AIR;\n\n        } else {\n            String[] line2 = getLine(2).split(\",\");\n            onBlock = api.getXLib().getExItem(line2[0]);\n            if (onBlock == null) {\n                markAsErroneous(\"Could not recognize onBlock, input: \" + getLine(2));\n                return;\n            }\n            if (line2.length > 1) {\n                onBlockData = (byte) NumberUtil.parseInt(line2[1]);\n            }\n        }\n\n        getSign().getBlock().setType(offBlock.getMaterial());\n        try {\n            setBlockData(getSign().getBlock(), offBlockData);\n        } catch (IllegalArgumentException exception) {\n            markAsErroneous(\"offBlock data value \" + offBlockData + \" cannot be applied to given type \" + offBlock.getId());\n        }\n    }\n\n    @Override\n    public void activate() {\n        getSign().getBlock().setType(onBlock.getMaterial());\n        try {\n            setBlockData(getSign().getBlock(), onBlockData);\n        } catch (IllegalArgumentException exception) {\n            markAsErroneous(\"onBlock data value \" + onBlockData + \" cannot be applied to given type \" + onBlock.getId());\n            return;\n        }\n        active = true;\n    }\n\n    @Override\n    public void deactivate() {\n        getSign().getBlock().setType(offBlock.getMaterial());\n        try {\n            setBlockData(getSign().getBlock(), offBlockData);\n        } catch (IllegalArgumentException exception) {\n            markAsErroneous(\"onBlock data value \" + offBlockData + \" cannot be applied to given type \" + onBlock.getId());\n            return;\n        }\n        active = false;\n    }\n\n    private static Method craftBlockSetData;\n\n    private static void setBlockData(Block block, byte data) {\n        if (data == Byte.MIN_VALUE) {\n            return;\n        }\n        if (craftBlockSetData == null) {\n            try {\n                String relocationTarget = RuntimeType.get().hasTrait(RuntimeTrait.OBC_RELOCATIONS) ? (Version.get().getRelocationTarget() + \".\") : \"\";\n                craftBlockSetData = Class.forName(\n                        \"org.bukkit.craftbukkit.\" + relocationTarget + \"block.CraftBlock\")\n                        .getDeclaredMethod(\"setData\", byte.class);\n            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalArgumentException exception) {\n                exception.printStackTrace();\n            }\n        }\n        try {\n            craftBlockSetData.invoke(block, data);\n        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/rocker/OpenDoorSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.rocker;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Rocker;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.dungeonsxl.world.block.LockedDoor;\nimport de.erethon.xlib.category.Category;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic class OpenDoorSign extends Rocker {\n\n    private LockedDoor door;\n\n    public OpenDoorSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public LockedDoor getDoor() {\n        return door;\n    }\n\n    public void setDoor(LockedDoor door) {\n        this.door = door;\n    }\n\n    @Override\n    public String getName() {\n        return \"Door\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".door\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        Block block = BlockUtilCompat.getAttachedBlock(getSign().getBlock());\n        if (Category.DOORS.containsBlock(block) || Category.FENCE_GATES.containsBlock(block) || Category.TRAPDOORS.containsBlock(block)) {\n            if (block.getRelative(BlockFace.DOWN).getType() == block.getType()) {\n                door = new LockedDoor(api, block.getRelative(BlockFace.DOWN));\n            } else {\n                door = new LockedDoor(api, block);\n            }\n            ((DGameWorld) getGameWorld()).addGameBlock(door);\n\n        } else {\n            markAsErroneous(\"No door attached\");\n        }\n    }\n\n    @Override\n    public void activate() {\n        if (door != null) {\n            door.open();\n            active = true;\n        }\n    }\n\n    @Override\n    public void deactivate() {\n        if (door != null) {\n            door.close();\n            active = false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/rocker/TriggerSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.rocker;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.sign.Rocker;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.SignTrigger;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class TriggerSign extends Rocker {\n\n    private int id = 0;\n\n    public TriggerSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    @Override\n    public String getName() {\n        return \"Trigger\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".trigger\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return true;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        Set<Integer> used = new HashSet<>();\n        for (DungeonSign dSign : getEditWorld().getDungeonSigns()) {\n            if (dSign instanceof TriggerSign) {\n                used.add(((TriggerSign) dSign).id);\n            }\n        }\n\n        if (getLine(1).isEmpty()) {\n            if (!used.isEmpty()) {\n                while (used.contains(id)) {\n                    id++;\n                }\n            }\n\n        } else {\n            id = NumberUtil.parseInt(getLine(1));\n            return !(id == 0 || used.contains(id));\n        }\n\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                getSign().setLine(1, String.valueOf(id));\n                getSign().update(true);\n            }\n        }.runTaskLater(api, 1L);\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        id = NumberUtil.parseInt(getLine(1), 1);\n    }\n\n    @Override\n    public void activate() {\n        SignTrigger trigger = SignTrigger.getById(id, getGameWorld());\n        if (trigger != null) {\n            trigger.trigger(true, null);\n        }\n    }\n\n    @Override\n    public boolean activate(Player player) {\n        SignTrigger trigger = SignTrigger.getById(id, getGameWorld());\n        if (trigger == null) {\n            return false;\n        }\n        trigger.trigger(true, player);\n        return true;\n    }\n\n    @Override\n    public void deactivate() {\n        SignTrigger trigger = SignTrigger.getById(id, getGameWorld());\n        if (trigger != null) {\n            trigger.trigger(false, null);\n        }\n    }\n\n    @Override\n    public boolean deactivate(Player player) {\n        SignTrigger trigger = SignTrigger.getById(id, getGameWorld());\n        if (trigger == null) {\n            return false;\n        }\n        trigger.trigger(false, player);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/CommandScript.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport java.io.File;\nimport java.util.List;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.configuration.file.YamlConfiguration;\nimport org.bukkit.permissions.Permission;\n\n/**\n * @author Daniel Saukel\n */\npublic class CommandScript {\n\n    private String name;\n    private File file;\n    private List<String> commands;\n\n    public CommandScript(String name, List<String> commands, Permission permission) {\n        this.name = name;\n        file = new File(DungeonsXL.COMMANDS, name + \".yml\");\n\n        setCommands(commands);\n    }\n\n    public CommandScript(File file) {\n        FileConfiguration config = YamlConfiguration.loadConfiguration(file);\n\n        name = file.getName().replace(\".yml\", \"\");\n        this.file = file;\n        setCommands(config.getStringList(\"commands\"));\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public File getFile() {\n        return file;\n    }\n\n    public List<String> getCommands() {\n        return commands;\n    }\n\n    public void setCommands(List<String> commands) {\n        this.commands = commands;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/CommandSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Windup;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.dungeonsxl.trigger.InteractTrigger;\nimport de.erethon.xlib.util.EnumUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Sign;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class CommandSign extends Windup {\n\n    public enum Executor {\n        DEFAULT,\n        OP,\n        CONSOLE\n    }\n\n    private CommandScript script;\n    private Executor executor;\n\n    public CommandSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public CommandScript getScript() {\n        return script;\n    }\n\n    @Override\n    public String getName() {\n        return \"CMD\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".cmd\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    public Executor getExecutor() {\n        return executor;\n    }\n\n    @Override\n    public boolean validate() {\n        script = ((DungeonsXL) api).getCommandScriptRegistry().get(getLine(1));\n        return script != null && !script.getCommands().isEmpty();\n    }\n\n    @Override\n    public void initialize() {\n        script = ((DungeonsXL) api).getCommandScriptRegistry().get(getLine(1));\n        String[] attributes = getLine(2).split(\",\");\n\n        if (attributes.length == 3) {\n            delay = NumberUtil.parseDouble(attributes[0]);\n            interval = NumberUtil.parseDouble(attributes[1]);\n            executor = EnumUtil.getEnumIgnoreCase(Executor.class, attributes[2]);\n            if (executor == null) {\n                executor = Executor.DEFAULT;\n            }\n\n        } else if (attributes.length == 2) {\n            delay = NumberUtil.parseDouble(attributes[0]);\n            interval = NumberUtil.parseDouble(attributes[1], -1);\n            if (interval == -1) {\n                interval = delay;\n                executor = EnumUtil.getEnumIgnoreCase(Executor.class, attributes[1]);\n            }\n            if (executor == null) {\n                executor = Executor.DEFAULT;\n            }\n\n        } else if (attributes.length == 1) {\n            delay = NumberUtil.parseDouble(attributes[0], -1);\n            if (delay == -1) {\n                delay = 0;\n                interval = 0;\n                executor = EnumUtil.getEnumIgnoreCase(Executor.class, attributes[0]);\n            }\n            if (executor == null) {\n                executor = Executor.DEFAULT;\n            }\n\n        } else if (attributes.length == 0) {\n            executor = Executor.DEFAULT;\n        }\n        n = script.getCommands().size();\n\n        setRunnable(new CommandTask(this, Bukkit.getPluginManager().isPluginEnabled(\"PlaceholderAPI\")));\n\n        if (!getTriggers().isEmpty()) {\n            setToAir();\n            return;\n        }\n\n        InteractTrigger.addDefault(api, this, script.getName(), \"\");\n    }\n\n    @Override\n    public void activate() {\n        if (executor == Executor.CONSOLE) {\n            ((CommandTask) getRunnable()).setSender(Bukkit.getConsoleSender(), false);\n            startTask();\n            active = true;\n        } else {\n            markAsErroneous(\"Sign is set to be performed by a player but is triggered by a trigger that cannot be attributed to a player (e.g. mob)\");\n        }\n    }\n\n    @Override\n    public boolean activate(Player player) {\n        CommandSender sender = player;\n        boolean wasOp = player.isOp();\n        if (executor == Executor.CONSOLE) {\n            sender = Bukkit.getConsoleSender();\n        }\n        ((CommandTask) getRunnable()).setSender(sender, wasOp);\n        ((CommandTask) getRunnable()).setPlayer(player);\n        startTask();\n        active = true;\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/CommandTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport me.clip.placeholderapi.PlaceholderAPI;\nimport org.bukkit.Bukkit;\nimport org.bukkit.command.CommandSender;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Daniel Saukel\n */\npublic class CommandTask extends BukkitRunnable {\n\n    private Player player;\n    private boolean wasOp;\n    private CommandSign sign;\n    private CommandScript script;\n    private CommandSender sender;\n    private boolean papi;\n\n    private int k;\n\n    public CommandTask(CommandSign sign, boolean papi) {\n        this.sign = sign;\n        this.script = sign.getScript();\n        this.papi = papi;\n    }\n\n    public void setSender(CommandSender sender, boolean wasOp) {\n        this.sender = sender;\n        this.wasOp = wasOp;\n    }\n\n    public void setPlayer(Player player) {\n        this.player = player;\n    }\n\n    @Override\n    public void run() {\n        if (sign.isWorldFinished()) {\n            sign.deactivate();\n            return;\n        }\n        if (k >= script.getCommands().size()) {\n            sign.deactivate();\n            k = 0;\n            return;\n        }\n\n        String command = script.getCommands().get(k++)\n                .replace(\"%world%\", sign.getGameWorld().getWorld().getName()).replace(\"%world_name%\", sign.getGameWorld().getWorld().getName());\n        if (player != null) {\n            command = command.replace(\"%player%\", player.getName()).replace(\"%player_name%\", player.getName());\n        }\n        if (sign.getExecutor() == CommandSign.Executor.OP) {\n            sender.setOp(true);\n        }\n        if (papi) {\n            Bukkit.getServer().dispatchCommand(sender, PlaceholderAPI.setPlaceholders(player, command));\n        } else {\n            Bukkit.getServer().dispatchCommand(sender, command);\n        }\n        if (sign.getExecutor() == CommandSign.Executor.OP) {\n            sender.setOp(wasOp);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/DelayedPowerTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class DelayedPowerTask extends BukkitRunnable {\n\n    private String worldName;\n    private RedstoneSign sign;\n    private boolean enable;\n\n    public DelayedPowerTask(RedstoneSign sign, boolean enable) {\n        worldName = sign.getSign().getWorld().getName();\n        this.sign = sign;\n        this.enable = enable;\n    }\n\n    @Override\n    public void run() {\n        if (Bukkit.getWorld(worldName) == null) {\n            sign.getEnableTask().cancel();\n            sign.getDisableTask().cancel();\n            return;\n        }\n\n        if (enable) {\n            sign.power();\n            if (sign.getRepeatsToDo() == 1) {\n                sign.getEnableTask().cancel();\n            }\n\n        } else {\n            sign.unpower();\n            if (sign.getRepeatsToDo() == 1) {\n                sign.getDisableTask().cancel();\n            }\n            sign.setRepeatsToDo(sign.getRepeatsToDo() - 1);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/DropSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Windup;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class DropSign extends Windup {\n\n    private ItemStack item;\n    private Location spawnLocation;\n\n    public DropSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public ItemStack getItem() {\n        return item;\n    }\n\n    public void setItem(ItemStack item) {\n        this.item = item;\n    }\n\n    @Override\n    public String getName() {\n        return \"Drop\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".drop\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return api.getXLib().getExItem(getLine(1)) != null;\n    }\n\n    @Override\n    public void initialize() {\n        ExItem item = api.getXLib().getExItem(getLine(1));\n\n        String[] attributes = getLine(2).split(\",\");\n        if (attributes.length >= 1) {\n            this.item = item.toItemStack(NumberUtil.parseInt(attributes[0], 1));\n        }\n        if (attributes.length == 2) {\n            interval = NumberUtil.parseDouble(attributes[1]);\n        }\n\n        spawnLocation = getSign().getLocation().add(0.5, 0, 0.5);\n        setRunnable(new BukkitRunnable() {\n            @Override\n            public void run() {\n                if (isWorldFinished()) {\n                    deactivate();\n                    return;\n                }\n                try {\n                    spawnLocation.getWorld().dropItem(spawnLocation, getItem());\n                } catch (NullPointerException exception) {\n                    deactivate();\n                }\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/MobSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.mob.ExternalMobProvider;\nimport de.erethon.dungeonsxl.api.sign.Windup;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.mob.ExMob;\nimport de.erethon.xlib.util.NumberUtil;\nimport de.erethon.xlib.util.Registry;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport org.bukkit.Location;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class MobSign extends Windup {\n\n    private Registry<String, ExternalMobProvider> providers;\n\n    private String mob;\n    private ExternalMobProvider provider;\n    private Collection<LivingEntity> spawnedMobs = new ArrayList<>();\n    private int initialAmount;\n\n    public MobSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n        providers = api.getExternalMobProviderRegistry();\n    }\n\n    public String getMob() {\n        return mob;\n    }\n\n    public void setMob(String mob) {\n        this.mob = mob;\n    }\n\n    /**\n     * Returns the initial amount of mobs to spawn - this value may increase with waves.\n     *\n     * @return the initial amount of mobs to spawn - this value may increase with waves\n     */\n    public int getInitialAmount() {\n        return initialAmount;\n    }\n\n    public Collection<LivingEntity> getSpawnedMobs() {\n        return spawnedMobs;\n    }\n\n    @Override\n    public String getName() {\n        return \"Mob\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".mob\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        if (getLine(1).isEmpty() || getLine(2).isEmpty()) {\n            return false;\n        }\n\n        String[] attributes = getLine(2).split(\",\");\n        return attributes.length == 2 || attributes.length == 3;\n    }\n\n    @Override\n    public void initialize() {\n        mob = getLine(1);\n        String[] attributes = getLine(2).split(\",\");\n\n        interval = NumberUtil.parseDouble(attributes[0]);\n        n = NumberUtil.parseInt(attributes[1]);\n        initialAmount = n;\n        provider = attributes.length == 3 ? providers.get(attributes[2]) : null;\n\n        setRunnable(new MobSpawnTask(api, this, n));\n    }\n\n    /**\n     * Spawns the mob.\n     *\n     * @return the spawned mob\n     */\n    public LivingEntity spawn() {\n        Location spawnLoc = getSign().getLocation().add(0.5, 0, 0.5);\n        LivingEntity spawned = null;\n\n        if (provider == null) {\n            ExMob type = api.getXLib().getExMob(mob);\n            if (type == null || !type.getSpecies().isAlive()) {\n                return null;\n            }\n            spawned = (LivingEntity) type.toEntity(spawnLoc);\n\n        } else {\n            provider.summon(mob, spawnLoc);\n            for (Entity entity : spawnLoc.getChunk().getEntities()) {\n                Location entityLoc = entity.getLocation();\n                if (entityLoc.getX() >= spawnLoc.getX() - 1 && entityLoc.getX() <= spawnLoc.getX() + 1 && entityLoc.getY() >= spawnLoc.getY() - 1\n                        && entityLoc.getY() <= spawnLoc.getY() + 1 && entityLoc.getZ() >= spawnLoc.getZ() - 1 && entityLoc.getZ() <= spawnLoc.getZ() + 1\n                        && entity instanceof LivingEntity && !spawnedMobs.contains((LivingEntity) entity) && !(entity instanceof Player)) {\n                    spawned = (LivingEntity) entity;\n                }\n            }\n        }\n\n        if (spawned == null) {\n            return null;\n        }\n\n        spawned.setRemoveWhenFarAway(false);\n        spawnedMobs.add(spawned);\n        return spawned;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/MobSpawnTask.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class MobSpawnTask extends BukkitRunnable {\n\n    private DungeonsAPI api;\n\n    private MobSign sign;\n    private int k = 1, n;\n\n    public MobSpawnTask(DungeonsAPI api, MobSign sign, int n) {\n        this.api = api;\n        this.sign = sign;\n        this.n = n;\n    }\n\n    @Override\n    public void run() {\n        if (sign.isWorldFinished()) {\n            sign.deactivate();\n            return;\n        }\n\n        LivingEntity entity = sign.spawn();\n        if (entity != null) {\n            api.wrapEntity(entity, sign.getGameWorld(), api.getXLib().getExMob(entity), sign.getMob());\n        }\n\n        if (k < n) {\n            k++;\n        } else {\n            sign.deactivate();\n            k = 1;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/sign/windup/RedstoneSign.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.sign.windup;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Rocker;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPermission;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.block.Sign;\nimport org.bukkit.scheduler.BukkitTask;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class RedstoneSign extends Rocker {\n\n    private BukkitTask enableTask;\n    private BukkitTask disableTask;\n    private long delay = 0;\n    private long offDelay = 0;\n    private int repeat = 1;\n    private int repeatsToDo = 1;\n\n    public RedstoneSign(DungeonsAPI api, Sign sign, String[] lines, InstanceWorld instance) {\n        super(api, sign, lines, instance);\n    }\n\n    public BukkitTask getEnableTask() {\n        return enableTask;\n    }\n\n    public void setEnableTask(BukkitTask enableTask) {\n        this.enableTask = enableTask;\n    }\n\n    public BukkitTask getDisableTask() {\n        return disableTask;\n    }\n\n    public void setDisableTask(BukkitTask disableTask) {\n        this.disableTask = disableTask;\n    }\n\n    public long getDelay() {\n        return delay;\n    }\n\n    public void setDelay(long delay) {\n        this.delay = delay;\n    }\n\n    public long getOffDelay() {\n        return offDelay;\n    }\n\n    public void setOffDelay(long offDelay) {\n        this.offDelay = offDelay;\n    }\n\n    public int getRepeat() {\n        return repeat;\n    }\n\n    public void setRepeat(int repeat) {\n        this.repeat = repeat;\n    }\n\n    public int getRepeatsToDo() {\n        return repeatsToDo;\n    }\n\n    public void setRepeatsToDo(int repeatsToDo) {\n        this.repeatsToDo = repeatsToDo;\n    }\n\n    @Override\n    public String getName() {\n        return \"Redstone\";\n    }\n\n    @Override\n    public String getBuildPermission() {\n        return DPermission.SIGN.getNode() + \".redstone\";\n    }\n\n    @Override\n    public boolean isOnDungeonInit() {\n        return false;\n    }\n\n    @Override\n    public boolean isProtected() {\n        return false;\n    }\n\n    @Override\n    public boolean isSetToAir() {\n        return true;\n    }\n\n    @Override\n    public boolean validate() {\n        return true;\n    }\n\n    @Override\n    public void initialize() {\n        int line1 = 0;\n        int line11 = 0;\n        if (!getLine(1).isEmpty()) {\n            String line[] = getLine(1).split(\",\");\n            line1 = NumberUtil.parseInt(line[0]);\n            if (line.length > 1) {\n                line11 = NumberUtil.parseInt(line[1]);\n            }\n        }\n\n        int line2 = 1;\n        if (!getLine(2).isEmpty()) {\n            line2 = NumberUtil.parseInt(getLine(2));\n        }\n\n        if (line1 > 0) {\n            delay = (long) line1 * 2;\n            if (line11 > 0) {\n                offDelay = (long) line11 * 2;\n            } else {\n                offDelay = delay;\n            }\n            if (line2 >= 0) {\n                repeat = line2;\n            }\n        }\n    }\n\n    @Override\n    public void activate() {\n        if (active) {\n            return;\n        }\n\n        if (delay > 0) {\n            enableTask = new DelayedPowerTask(this, true).runTaskTimer(api, delay, delay + offDelay);\n\n            if (repeat != 1) {\n                repeatsToDo = repeat;\n                disableTask = new DelayedPowerTask(this, false).runTaskTimer(api, delay + offDelay, delay + offDelay);\n            }\n\n        } else {\n            power();\n        }\n\n        active = true;\n    }\n\n    @Override\n    public void deactivate() {\n        if (!active) {\n            return;\n        }\n\n        unpower();\n\n        if (enableTask != null) {\n            enableTask.cancel();\n        }\n        if (disableTask != null) {\n            disableTask.cancel();\n        }\n\n        active = false;\n    }\n\n    public void power() {\n        getSign().getBlock().setType(VanillaItem.REDSTONE_BLOCK.getMaterial());\n    }\n\n    public void unpower() {\n        setToAir();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/DistanceTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.Location;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DistanceTrigger extends AbstractTrigger {\n\n    private int distance = 5;\n    private Location loc;\n\n    public DistanceTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n\n        distance = NumberUtil.parseInt(value, distance);\n        if (distance < 2) {\n            distance = 2;\n        }\n        this.loc = owner.getLocation();\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.DISTANCE;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n        unregisterTrigger();\n        getListeners().clear();\n        getGameWorld().unregisterTrigger(this);\n    }\n\n    /* Statics */\n    public static void triggerAllInDistance(Player player, DGameWorld gameWorld) {\n        if (!player.getLocation().getWorld().equals(gameWorld.getWorld())) {\n            return;\n        }\n\n        for (Trigger trigger : gameWorld.getTriggers().toArray(Trigger[]::new)) {\n            if (!(trigger instanceof DistanceTrigger)) {\n                continue;\n            }\n            DistanceTrigger distanceTrigger = (DistanceTrigger) trigger;\n            if (player.getLocation().distance(distanceTrigger.loc) < distanceTrigger.distance) {\n                distanceTrigger.trigger(true, player);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/FortuneTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.Random;\n\n/**\n * @author Daniel Saukel\n */\npublic class FortuneTrigger extends AbstractTrigger {\n\n    private double chance = 0;\n\n    public FortuneTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        this.chance = NumberUtil.parseDouble(value, chance);\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.FORTUNE;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the chance\n     */\n    public double getChance() {\n        return chance;\n    }\n\n    /**\n     * @param chance the chance to set\n     */\n    public void setChance(double chance) {\n        this.chance = chance;\n    }\n\n    /* Actions */\n    @Override\n    public void onTrigger(boolean switching) {\n        int random = new Random().nextInt(100);\n        if (chance * 100 >= random) {\n            setTriggered(true);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/InteractTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.util.NumberUtil;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class InteractTrigger extends AbstractTrigger {\n\n    private static int unusedId = Integer.MIN_VALUE;\n\n    private int id;\n    private Block interactBlock;\n\n    public InteractTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        id = NumberUtil.parseInt(value);\n        interactBlock = getGameWorld().getWorld().getBlockAt(owner.getLocation());\n    }\n\n    private InteractTrigger(DungeonsAPI api, TriggerListener owner) {\n        super(api, owner, LogicalExpression.parse(\"I\" + unusedId), String.valueOf(unusedId++));\n        interactBlock = getGameWorld().getWorld().getBlockAt(owner.getLocation());\n    }\n\n    public Block getInteractBlock() {\n        return interactBlock;\n    }\n\n    public void setInteractBlock(Block block) {\n        interactBlock = block;\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.INTERACT;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n    }\n\n    /* Statics */\n    public static InteractTrigger getByBlock(Block block, GameWorld gameWorld) {\n        if (block == null || gameWorld == null) {\n            return null;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof InteractTrigger)) {\n                continue;\n            }\n            InteractTrigger trigger = (InteractTrigger) uncasted;\n            if (block.equals(trigger.interactBlock)) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n    public static InteractTrigger getById(int id, GameWorld gameWorld) {\n        if (gameWorld == null) {\n            return null;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof InteractTrigger)) {\n                continue;\n            }\n            InteractTrigger trigger = (InteractTrigger) uncasted;\n            if (id == trigger.id) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n    public static void addDefault(DungeonsAPI api, DungeonSign dungeonSign, String line1, String line2) {\n        InteractTrigger trigger = new InteractTrigger(api, dungeonSign);\n        trigger.addListener(dungeonSign);\n\n        Sign sign = dungeonSign.getSign();\n        sign.setLine(0, ChatColor.DARK_BLUE + \"############\");\n        sign.setLine(1, ChatColor.GREEN + line1);\n        sign.setLine(2, ChatColor.GREEN + line2);\n        sign.setLine(3, ChatColor.DARK_BLUE + \"############\");\n        sign.update();\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/MobTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class MobTrigger extends AbstractTrigger {\n\n    private String name;\n\n    public MobTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        name = value;\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.MOB;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n    }\n\n    /* Statics */\n    public static MobTrigger getByName(String name, GameWorld gameWorld) {\n        if (name == null || gameWorld == null) {\n            return null;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof MobTrigger)) {\n                continue;\n            }\n            MobTrigger trigger = (MobTrigger) uncasted;\n            if (name.equalsIgnoreCase(trigger.name)) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/PresenceTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\n\n/**\n * @author Daniel Saukel\n */\npublic class PresenceTrigger extends DistanceTrigger {\n\n    public PresenceTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.PRESENCE;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n        unregisterTrigger();\n        getListeners().clear();\n        getGameWorld().unregisterTrigger(this);\n    }\n\n    @Override\n    public void postTrigger() {\n        setTriggered(false);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/ProgressTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.world.DResourceWorld;\nimport de.erethon.xlib.util.NumberUtil;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class ProgressTrigger extends AbstractTrigger {\n\n    private DResourceWorld floor;\n    private int floorCount;\n    private int waveCount;\n\n    // Unused\n    public ProgressTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        this(api, owner, expression, value, NumberUtil.parseInt(value.split(\"/\")[0]), NumberUtil.parseInt(value.split(\"/\")[1]));\n    }\n\n    public ProgressTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value, int floorCount, int waveCount) {\n        super(api, owner, expression, value);\n        this.floorCount = floorCount;\n        this.waveCount = waveCount;\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.PROGRESS;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the specific floor that must be finished\n     */\n    public DResourceWorld getFloor() {\n        return floor;\n    }\n\n    /**\n     * @param floor the specific floor to set\n     */\n    public void setFloor(DResourceWorld floor) {\n        this.floor = floor;\n    }\n\n    /**\n     * @return the floor count to trigger\n     */\n    public int getFloorCount() {\n        return floorCount;\n    }\n\n    /**\n     * @param floorCount the floor count to set\n     */\n    public void setFloorCount(int floorCount) {\n        this.floorCount = floorCount;\n    }\n\n    /**\n     * @return the wave count to trigger\n     */\n    public int getWaveCount() {\n        return waveCount;\n    }\n\n    /**\n     * @param waveCount the wave count to set\n     */\n    public void setWaveCount(int waveCount) {\n        this.waveCount = waveCount;\n    }\n\n    /* Actions */\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n    }\n\n    /* Statics */\n    public static ProgressTrigger getOrCreate(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        String[] values = value.split(\"/\");\n        int floorCount = NumberUtil.parseInt(values[0]);\n        int waveCount = NumberUtil.parseInt(values[1]);\n        if (floorCount == 0 & waveCount == 0 || floorCount < 0 || waveCount < 0) {\n            return null;\n        }\n        return new ProgressTrigger(api, owner, expression, value, floorCount, waveCount);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/RedstoneTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.sign.Deactivatable;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.xlib.category.Category;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class RedstoneTrigger extends AbstractTrigger {\n\n    private Block rtBlock;\n\n    public RedstoneTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n\n        Location loc = owner.getLocation();\n        if (Category.WALL_SIGNS.containsBlock(loc.getBlock())) {\n            rtBlock = BlockUtilCompat.getAttachedBlock(loc.getBlock());\n        } else {\n            rtBlock = loc.getBlock();\n        }\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.REDSTONE;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        if (rtBlock.isBlockPowered()) {\n            if (!isTriggered()) {\n                setTriggered(true);\n            }\n\n        } else if (isTriggered()) {\n            setTriggered(false);\n\n            for (TriggerListener listener : getListeners().toArray(TriggerListener[]::new)) {\n                if (!(listener instanceof Deactivatable)) {\n                    return;\n                }\n                Deactivatable sign = ((Deactivatable) listener);\n                if (sign.isErroneous()) {\n                    return;\n                }\n                for (Trigger trigger : sign.getTriggers()) {\n                    if (trigger.isTriggered()) {\n                        return;\n                    }\n                }\n                sign.deactivate();\n            }\n        }\n    }\n\n    /* Statics */\n    public static void updateAll(GameWorld gameWorld) {\n        if (gameWorld == null) {\n            return;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof RedstoneTrigger)) {\n                continue;\n            }\n            ((RedstoneTrigger) uncasted).trigger(true, null);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/SignTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.util.NumberUtil;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class SignTrigger extends AbstractTrigger {\n\n    private int id;\n\n    public SignTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        char key = expression.getText().charAt(0);\n        int i = Character.toUpperCase(key) == getKey() ? 1 : 0;\n        id = NumberUtil.parseInt(expression.getText().substring(i));\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.GENERIC;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        if (switching != isTriggered()) {\n            setTriggered(switching);\n        }\n    }\n\n    /* Statics */\n    public static SignTrigger getById(int id, GameWorld gameWorld) {\n        if (gameWorld == null) {\n            return null;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof SignTrigger)) {\n                continue;\n            }\n            SignTrigger trigger = (SignTrigger) uncasted;\n            if (id == trigger.id) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/TriggerListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.xlib.item.VanillaItem;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.Action;\nimport org.bukkit.event.block.BlockRedstoneEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.meta.BookMeta;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Milan Albrecht, Daniel Saukel\n */\npublic class TriggerListener implements Listener {\n\n    private DungeonsXL plugin;\n\n    public TriggerListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n    }\n\n    @EventHandler\n    public void onBlockRedstone(final BlockRedstoneEvent event) {\n        new BukkitRunnable() {\n            @Override\n            public void run() {\n                GameWorld gameWorld = plugin.getGameWorld(event.getBlock().getWorld());\n                if (gameWorld != null) {\n                    RedstoneTrigger.updateAll(gameWorld);\n                }\n            }\n        }.runTaskLater(plugin, 1L);\n    }\n\n    @EventHandler\n    public void onPlayerInteract(PlayerInteractEvent event) {\n        if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) {\n            return;\n        }\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        GameWorld gameWorld = plugin.getGameWorld(player.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        ItemStack item = event.getItem();\n        if (item == null) {\n            return;\n        }\n        String name = null;\n        if (item.hasItemMeta()) {\n            if (item.getItemMeta().hasDisplayName()) {\n                name = item.getItemMeta().getDisplayName();\n\n            } else if (VanillaItem.WRITTEN_BOOK.is(item) || VanillaItem.WRITABLE_BOOK.is(item)) {\n                if (item.getItemMeta() instanceof BookMeta) {\n                    BookMeta meta = (BookMeta) item.getItemMeta();\n                    if (meta.hasTitle()) {\n                        name = meta.getTitle();\n                    }\n                }\n            }\n        }\n        if (name == null) {\n            name = plugin.getXLib().getExItem(item).getName();\n        }\n\n        UseItemTrigger trigger = UseItemTrigger.getByName(name, gameWorld);\n        if (trigger != null) {\n            trigger.trigger(true, player);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/UseItemTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.xlib.item.ExItem;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class UseItemTrigger extends AbstractTrigger {\n\n    private String name;\n    private String matchedName;\n\n    public UseItemTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        name = value;\n        ExItem item = api.getXLib().getExItem(name);\n        if (item != null) {\n            matchedName = item.toString();\n        }\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.USE_ITEM;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n    }\n\n    /* Statics */\n    public static UseItemTrigger getByName(String name, GameWorld gameWorld) {\n        if (name == null || gameWorld == null) {\n            return null;\n        }\n        for (Trigger uncasted : gameWorld.getTriggers()) {\n            if (!(uncasted instanceof UseItemTrigger)) {\n                continue;\n            }\n            UseItemTrigger trigger = (UseItemTrigger) uncasted;\n            if (name.equalsIgnoreCase(trigger.name)) {\n                return trigger;\n            } else if (name.equalsIgnoreCase(trigger.matchedName)) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/trigger/WaveTrigger.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.trigger;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.trigger.AbstractTrigger;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.xlib.util.NumberUtil;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class WaveTrigger extends AbstractTrigger {\n\n    private double mustKillRate = 1;\n\n    public WaveTrigger(DungeonsAPI api, TriggerListener owner, LogicalExpression expression, String value) {\n        super(api, owner, expression, value);\n        mustKillRate = NumberUtil.parseDouble(value, mustKillRate);\n    }\n\n    @Override\n    public char getKey() {\n        return TriggerTypeKey.WAVE;\n    }\n\n    /**\n     * @return the minimal mob kill rate to trigger the wave\n     */\n    public double getMustKillRate() {\n        return mustKillRate;\n    }\n\n    /**\n     * @param mustKillRate the minimal mob kill rate to trigger the wave to set\n     */\n    public void setMustKillRate(double mustKillRate) {\n        this.mustKillRate = mustKillRate;\n    }\n\n    @Override\n    public void onTrigger(boolean switching) {\n        setTriggered(true);\n    }\n\n    @Override\n    public void postTrigger() {\n        setTriggered(false);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/AttributeUtil.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport com.google.common.collect.ImmutableMap;\nimport de.erethon.xlib.compatibility.Version;\nimport java.util.Map;\nimport org.bukkit.Registry;\nimport org.bukkit.attribute.Attribute;\nimport org.bukkit.attribute.AttributeInstance;\nimport org.bukkit.entity.Player;\n\n/**\n * @author Daniel Saukel\n */\npublic class AttributeUtil {\n\n    public static final Attribute ATTACK_DAMAGE = Attribute.valueOf(Version.isAtLeast(Version.MC1_21_2) ? \"ATTACK_DAMAGE\" : \"GENERIC_ATTACK_DAMAGE\");\n    public static final Attribute MAX_HEALTH = Attribute.valueOf(Version.isAtLeast(Version.MC1_21_2) ? \"MAX_HEALTH\" : \"GENERIC_MAX_HEALTH\");\n    public static final Attribute MOVEMENT_SPEED = Attribute.valueOf(Version.isAtLeast(Version.MC1_21_2) ? \"MOVEMENT_SPEED\" : \"GENERIC_MOVEMENT_SPEED\");\n\n    private static final Map<Attribute, Double> DEFAULT_PLAYER_VALUES = ImmutableMap.of(\n            MOVEMENT_SPEED, .1, // .7\n            ATTACK_DAMAGE, 1.0 // 2.0\n    );\n\n    /**\n     * Returns the attribute represented by the key.\n     *\n     * @param key the key; not null\n     * @return the attribute represented by the key\n     */\n    public static Attribute get(String key) {\n        Attribute attribute;\n        if (Version.isAtLeast(Version.MC1_21_2)) {\n            attribute = Registry.ATTRIBUTE.match(key);\n            if (attribute == null) {\n                // Compatibility upon Minecraft updates\n                attribute = Registry.ATTRIBUTE.match(key.replace(\"GENERIC_\", \"\"));\n            }\n        } else {\n            try {\n                attribute = Attribute.valueOf(key);\n            } catch (Exception exception) {\n                attribute = null;\n            }\n        }\n        return attribute;\n    }\n\n    /**\n     * Returns the default value that a player entity has.\n     *\n     * @param attribute the attribute instance to check\n     * @return the default value that a player entity has\n     */\n    public static final Double getDefaultPlayerValue(AttributeInstance attribute) {\n        return DEFAULT_PLAYER_VALUES.getOrDefault(attribute.getAttribute(), attribute.getDefaultValue());\n    }\n\n    /**\n     * Resets a player's attributes.\n     *\n     * @param player the player\n     */\n    public static void resetPlayerAttributes(Player player) {\n        for (Attribute attribute : Attribute.values()) {\n            AttributeInstance instance = player.getAttribute(attribute);\n            if (instance == null) {\n                continue;\n            }\n            instance.setBaseValue(getDefaultPlayerValue(instance));\n            instance.getModifiers().forEach(instance::removeModifier);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/BlockUtilCompat.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport de.erethon.xlib.compatibility.Version;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.block.data.Directional;\nimport org.bukkit.material.Attachable;\nimport org.bukkit.material.MaterialData;\n\n/**\n * @author Daniel Saukel\n */\npublic class BlockUtilCompat {\n\n    /**\n     * Returns the block the given block is attached to.\n     *\n     * @param block the block to check\n     * @return the attached block\n     */\n    public static Block getAttachedBlock(Block block) {\n        if (Version.isAtLeast(Version.MC1_13)) {\n            if (block.getBlockData() instanceof Directional) {\n                Directional data = (Directional) block.getBlockData();\n                if (data.getFaces().size() == 4) {\n                    return block.getRelative(data.getFacing().getOppositeFace());\n                }\n            }\n            return block.getRelative(BlockFace.DOWN);\n\n        } else {\n            MaterialData meta = block.getState().getData();\n            BlockFace blockFace = BlockFace.DOWN;\n            if (meta instanceof Attachable) {\n                blockFace = ((Attachable) meta).getAttachedFace();\n            }\n            return block.getRelative(blockFace);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/ContainerAdapter.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport de.erethon.xlib.compatibility.Version;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Chest;\nimport org.bukkit.block.Container;\nimport org.bukkit.inventory.Inventory;\nimport org.bukkit.inventory.InventoryHolder;\n\n/**\n * @author Daniel Saukel\n */\npublic class ContainerAdapter {\n\n    public static boolean isValidContainer(Block block) {\n        if (Version.isAtLeast(Version.MC1_12_1)) {\n            return block.getState() instanceof Container;\n        } else {\n            return block.getState() instanceof Chest;\n        }\n    }\n\n    public static boolean isValidContainer(Inventory inventory) {\n        if (Version.isAtLeast(Version.MC1_12_1)) {\n            return inventory.getHolder() instanceof Container;\n        } else {\n            return inventory.getHolder() instanceof Chest;\n        }\n    }\n\n    public static Block getHolderBlock(InventoryHolder holder) {\n        if (Version.isAtLeast(Version.MC1_12_1)) {\n            return ((Container) holder).getBlock();\n        } else {\n            return ((Chest) holder).getBlock();\n        }\n    }\n\n    public static Inventory getBlockInventory(Block block) {\n        if (Version.isAtLeast(Version.MC1_12_1)) {\n            return ((Container) block.getState()).getInventory();\n        } else {\n            return ((Chest) block.getState()).getInventory();\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/DependencyVersion.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.plugin.PluginMeta;\nimport de.erethon.xlib.spiget.comparator.VersionComparator;\nimport java.io.IOException;\nimport java.util.Properties;\nimport java.util.function.Predicate;\nimport org.bukkit.Bukkit;\nimport org.bukkit.plugin.Plugin;\n\n/**\n * Lists compatible plugin versions.\n *\n * @author Daniel Saukel\n */\npublic enum DependencyVersion {\n\n    XLIB(\"XLib-Runtime\", getProperties().getProperty(\"dependencyVersion.xlib\")),\n    BOSSSHOP(\"BossShop\", getProperties().getProperty(\"dependencyVersion.bossshop\")),\n    CITIZENS(\"Citizens\", getProperties().getProperty(\"dependencyVersion.citizens\")),\n    HOLOGRAPHIC_DISPLAYS(\"HolographicDisplays\", getProperties().getProperty(\"dependencyVersion.holographicdisplays\")),\n    MODERN_LWC(\"LWC\", \"2.1.5-09ad392\"),\n    PARTIES(\"Parties\", getProperties().getProperty(\"dependencyVersion.parties\")),\n    PLACEHOLDER_API(\"PlaceholderAPI\", getProperties().getProperty(\"dependencyVersion.placeholderapi\")),\n    VAULT(\"Vault\", \"1.7.3-b131\"),\n    // Two public plugins share this name\n    CUSTOM_MOBS(\"CustomMobs\", \"4.17\", s -> {\n        try {\n            Class.forName(\"de.hellfirepvp.CustomMobs\");\n            return true;\n        } catch (ClassNotFoundException e) {\n            return false;\n        }\n    }),\n    INSANE_MOBS(\"InsaneMobs2\", \"3.0.1\"),\n    MYTHIC_MOBS(\"MythicMobs\", \"5.11.2-6a371d59\");\n\n    /**\n     * Meta information about this project.\n     */\n    public static final PluginMeta META = new PluginMeta.Builder(\"DungeonsXL\")\n            .minVersion(Version.MC1_8_8)\n            .paperState(PluginMeta.State.NOT_SUPPORTED)\n            .spigotState(PluginMeta.State.SUPPORTED)\n            .economyState(PluginMeta.State.SUPPORTED)\n            .permissionsState(PluginMeta.State.SUPPORTED)\n            .spigotMCResourceId(9488)\n            .bStatsResourceId(1039)\n            .versionComparator(VersionComparator.SEM_VER_SNAPSHOT)\n            .build();\n\n    private static Properties properties;\n\n    private String name;\n    private String version;\n    private Plugin plugin;\n\n    DependencyVersion(String name, String version) {\n        this(name, version, null);\n    }\n\n    DependencyVersion(String name, String version, Predicate<String> enabled) {\n        this.name = name;\n        this.version = version;\n        if (enabled == null || enabled.test(name)) {\n            plugin = Bukkit.getPluginManager().getPlugin(name);\n        }\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getSupportedVersion() {\n        return version;\n    }\n\n    public String getEnabledVersion() {\n        return plugin.getDescription().getVersion();\n    }\n\n    public boolean isEnabled() {\n        return plugin != null;\n    }\n\n    public boolean check() {\n        return isEnabled() && getSupportedVersion().equals(getEnabledVersion());\n    }\n\n    public static Properties getProperties() {\n        if (properties == null) {\n            properties = new Properties();\n            try {\n                properties.load(DungeonsXL.class.getClassLoader().getResourceAsStream(\"dxl.properties\"));\n            } catch (IOException exception) {\n                exception.printStackTrace();\n            }\n        }\n        return properties;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/LWCUtil.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport com.griefcraft.lwc.LWC;\nimport com.griefcraft.model.Protection;\nimport org.bukkit.Bukkit;\nimport org.bukkit.block.Block;\n\n/**\n * @author Daniel Saukel\n */\npublic class LWCUtil {\n\n    public static void removeProtection(Block block) {\n        if (!isLWCLoaded()) {\n            return;\n        }\n        Protection protection = LWC.getInstance().getProtectionCache().getProtection(block);\n        if (protection != null) {\n            protection.remove();\n        }\n    }\n\n    /**\n     * @return true if LWC is loaded\n     */\n    public static boolean isLWCLoaded() {\n        return Bukkit.getPluginManager().getPlugin(\"LWC\") != null;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/LocationString.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.World;\n\n/**\n * @author Daniel Saukel\n */\npublic class LocationString {\n\n    private String raw;\n    private String world;\n    private double x, y, z;\n    private float yaw, pitch;\n    private Location location;\n\n    private LocationString(String string) {\n        raw = string;\n    }\n\n    public static LocationString fromString(String string) {\n        if (string == null) {\n            return null;\n        }\n\n        String[] args = string.split(\",\");\n        if (args.length < 4 || args.length == 5 || args.length > 6) {\n            return null;\n        }\n\n        double x, y, z;\n        float yaw, pitch;\n        try {\n            x = Double.parseDouble(args[1]);\n            y = Double.parseDouble(args[2]);\n            z = Double.parseDouble(args[3]);\n            if (args.length == 6) {\n                yaw = Float.parseFloat(args[4]);\n                pitch = Float.parseFloat(args[5]);\n            } else {\n                yaw = 0f;\n                pitch = 0f;\n            }\n        } catch (NumberFormatException exception) {\n            return null;\n        }\n\n        LocationString locationString = new LocationString(string);\n        locationString.world = args[0];\n        locationString.x = x;\n        locationString.y = y;\n        locationString.z = z;\n        locationString.yaw = yaw;\n        locationString.pitch = pitch;\n        return locationString;\n    }\n\n    public Location getLocation() {\n        if (location == null) {\n            World bukkitWorld = Bukkit.getWorld(world);\n            if (bukkitWorld == null) {\n                return null;\n            }\n            location = new Location(bukkitWorld, x, y, z, yaw, pitch);\n        }\n        return location;\n    }\n\n    @Override\n    public String toString() {\n        return raw;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/ParsingUtil.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport de.erethon.dungeonsxl.api.player.GlobalPlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\n\n/**\n * @author Daniel Saukel\n */\npublic enum ParsingUtil {\n\n    GROUP_COLOR(\"%group_color%\"),\n    GROUP_NAME(\"%group_name%\"),\n    PLAYER_NAME(\"%player_name%\");\n\n    private String placeholder;\n\n    ParsingUtil(String placeholder) {\n        this.placeholder = placeholder;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the placeholder\n     */\n    public String getPlaceholder() {\n        return placeholder;\n    }\n\n    @Override\n    public String toString() {\n        return placeholder;\n    }\n\n    /* Statics */\n    /**\n     * Replace the placeholders that are relevant for the chat in a String automatically.\n     *\n     * @param string the String that contains the placeholders\n     * @param sender the GlobalPlayer who sent the message\n     * @return the string with the placeholders replaced\n     */\n    public static String replaceChatPlaceholders(String string, GlobalPlayer sender) {\n        string = string.replaceAll(PLAYER_NAME.getPlaceholder(), sender.getName());\n\n        DGroup group = (DGroup) sender.getGroup();\n        if (group != null) {\n            string = string.replaceAll(GROUP_COLOR.getPlaceholder(), group.getDColor().getChatColor().toString());\n            string = string.replaceAll(GROUP_NAME.getPlaceholder(), group.getName());\n        }\n\n        return string;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/util/PlaceholderUtil.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.util;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport me.clip.placeholderapi.expansion.PlaceholderExpansion;\nimport org.bukkit.OfflinePlayer;\n\n/**\n * @author Daniel Saukel\n */\npublic class PlaceholderUtil extends PlaceholderExpansion {\n\n    private DungeonsXL plugin;\n\n    private String identifier;\n\n    public PlaceholderUtil(DungeonsXL plugin, String identifier) {\n        this.plugin = plugin;\n        this.identifier = identifier;\n    }\n\n    @Override\n    public String getAuthor() {\n        return plugin.getDescription().getAuthors().toString();\n    }\n\n    @Override\n    public String getIdentifier() {\n        return identifier;\n    }\n\n    @Override\n    public String getRequiredPlugin() {\n        return plugin.getName();\n    }\n\n    @Override\n    public String getVersion() {\n        return plugin.getDescription().getVersion();\n    }\n    \n    @Override\n    public boolean persist() {\n        return true;\n    }\n\n    @Override\n    public String onRequest(OfflinePlayer player, String identifier) {\n        if (player == null) {\n            return \"\";\n        }\n        PlayerGroup group = plugin.getPlayerGroup(player.getPlayer());\n\n        switch (identifier) {\n            case \"group_members\":\n                return group != null ? group.getMembers().getNames().toString().substring(1, group.getMembers().getNames().toString().length() - 1) : \"\";\n            case \"group_name\":\n                return group != null ? group.getName() : \"\";\n            case \"group_name_raw\":\n                return group != null ? group.getRawName() : \"\";\n            case \"group_player_count\":\n                return group != null ? String.valueOf(group.getMembers().size()) : \"\";\n            case \"game_player_count\":\n                Game game = group.getGame();\n                return game != null ? String.valueOf(game.getPlayers().size()) : \"\";\n            case \"floor_player_count\":\n                GameWorld gameWorld = group.getGameWorld();\n                return gameWorld != null ? String.valueOf(gameWorld.getPlayers().size()) : \"\";\n            case \"dungeon_name\":\n                return group != null ? group.getDungeon().getName() : \"\";\n            case \"global_dungeon_count\":\n                return String.valueOf(plugin.getDungeonRegistry().size());\n            case \"global_floor_count\":\n                return String.valueOf(plugin.getMapRegistry().size());\n            case \"global_instance_count\":\n                return String.valueOf(plugin.getInstanceCache().size());\n            default:\n                return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/DEditWorld.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.event.world.EditWorldSaveEvent;\nimport de.erethon.dungeonsxl.api.event.world.EditWorldUnloadEvent;\nimport de.erethon.dungeonsxl.api.event.world.InstanceWorldPostUnloadEvent;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.mob.CitizensMobProvider;\nimport de.erethon.dungeonsxl.player.DEditPlayer;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.util.FileUtil;\nimport de.erethon.xlib.util.ProgressBar;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Player;\nimport org.bukkit.scheduler.BukkitRunnable;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class DEditWorld extends DInstanceWorld implements EditWorld {\n\n    public static String ID_FILE_PREFIX = \".id_\";\n\n    private File idFile;\n\n    DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder) {\n        super(plugin, resourceWorld, folder);\n    }\n\n    /* Getters and setters */\n    /**\n     * Returns the file that stores the ID\n     *\n     * @return the file that stores the ID\n     */\n    public File getIdFile() {\n        return idFile;\n    }\n\n    /**\n     * Generates an ID file for identification upon server restarts\n     */\n    public void generateIdFile() {\n        try {\n            idFile = new File(getFolder(), ID_FILE_PREFIX + getName());\n            idFile.createNewFile();\n\n        } catch (IOException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n    /* Actions */\n    @Override\n    public void registerSign(Block block) {\n        if (block.getState() instanceof Sign) {\n            Sign sign = (Sign) block.getState();\n            String[] lines = sign.getLines();\n\n            if (lines[0].equalsIgnoreCase(\"[lobby]\")) {\n                setLobbyLocation(block.getLocation());\n            }\n        }\n    }\n\n    @Override\n    public void save() {\n        EditWorldSaveEvent event = new EditWorldSaveEvent(this);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        plugin.setLoadingWorld(true);\n        Map<Player, double[]> players = new HashMap<>();\n        getWorld().getPlayers().forEach(p -> players.put(p,\n                new double[]{\n                    p.getLocation().getX(),\n                    p.getLocation().getY(),\n                    p.getLocation().getZ(),\n                    p.getLocation().getYaw(),\n                    p.getLocation().getPitch()\n                }\n        ));\n        kickAllPlayers();\n\n        getResource().editWorld = null;\n        plugin.getInstanceCache().remove(this);\n        getResource().getSignData().serializeSigns(signs.values());\n        Bukkit.unloadWorld(getWorld(), true);\n        new ProgressBar(players.keySet(), plugin.getMainConfig().getEditInstanceRemovalDelay()) {\n            @Override\n            public void onFinish() {\n                getResource().clearFolder();\n                FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES);\n                DResourceWorld.deleteUnusedFiles(getResource().getFolder());\n                FileUtil.removeDir(getFolder());\n\n                plugin.setLoadingWorld(false);\n                EditWorld newEditWorld = getResource().getOrInstantiateEditWorld(true);\n                players.keySet().forEach(p -> {\n                    if (p.isOnline()) {\n                        new DEditPlayer(plugin, p, newEditWorld);\n                        double[] coords = players.get(p);\n                        p.teleport(new Location(newEditWorld.getWorld(), coords[0], coords[1], coords[2], (float) coords[3], (float) coords[4]));\n                    }\n                });\n            }\n        }.send(plugin);\n    }\n\n    public void forceSave() {\n        EditWorldSaveEvent event = new EditWorldSaveEvent(this);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        getWorld().save();\n\n        FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES);\n        DResourceWorld.deleteUnusedFiles(getResource().getFolder());\n\n        getResource().getSignData().serializeSigns(signs.values());\n    }\n\n    @Override\n    public void delete() {\n        delete(true);\n    }\n\n    @Override\n    public void delete(boolean save) {\n        EditWorldUnloadEvent event = new EditWorldUnloadEvent(this, true);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        if (Bukkit.getPluginManager().getPlugin(\"Citizens\") != null) {\n            ((CitizensMobProvider) plugin.getExternalMobProviderRegistry().get(\"CI\")).removeSpawnedNPCs(getWorld());\n        }\n\n        kickAllPlayers();\n\n        String name = getWorld().getName();\n        if (save) {\n            getResource().getSignData().serializeSigns(signs.values());\n            boolean unloaded = Bukkit.unloadWorld(getWorld(), true);\n            if (!unloaded) {\n                MessageUtil.debug(plugin, \"Error: Could not unload world \" + getWorld());\n            }\n            new BukkitRunnable() {\n                @Override\n                public void run() {\n                    getResource().clearFolder();\n                    FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES);\n                    DResourceWorld.deleteUnusedFiles(getResource().getFolder());\n                    if (unloaded) {\n                        FileUtil.removeDir(getFolder());\n                    }\n                    Bukkit.getPluginManager().callEvent(new InstanceWorldPostUnloadEvent(getResource(), name));\n                }\n            }.runTaskLater(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L);\n        }\n        if (!save) {\n            boolean unloaded = Bukkit.unloadWorld(getWorld(), /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4));\n            if (!unloaded) {\n                MessageUtil.debug(plugin, \"Error: Could not unload world \" + getWorld());\n            }\n            new BukkitRunnable() {\n                @Override\n                public void run() {\n                    if (unloaded) {\n                        FileUtil.removeDir(getFolder());\n                    }\n                    Bukkit.getPluginManager().callEvent(new InstanceWorldPostUnloadEvent(getResource(), name));\n                }\n            }.runTaskLater(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L);\n        }\n\n        getResource().editWorld = null;\n        plugin.getInstanceCache().remove(this);\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.BuildMode;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.event.trigger.TriggerRegistrationEvent;\nimport de.erethon.dungeonsxl.api.event.world.GameWorldStartGameEvent;\nimport de.erethon.dungeonsxl.api.event.world.InstanceWorldPostUnloadEvent;\nimport de.erethon.dungeonsxl.api.event.world.InstanceWorldUnloadEvent;\nimport de.erethon.dungeonsxl.api.mob.DungeonMob;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.trigger.LogicalExpression;\nimport de.erethon.dungeonsxl.api.trigger.Trigger;\nimport de.erethon.dungeonsxl.api.trigger.TriggerListener;\nimport de.erethon.dungeonsxl.api.trigger.TriggerTypeKey;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.mob.CitizensMobProvider;\nimport de.erethon.dungeonsxl.sign.button.ReadySign;\nimport de.erethon.dungeonsxl.sign.passive.StartSign;\nimport de.erethon.dungeonsxl.sign.windup.MobSign;\nimport de.erethon.dungeonsxl.trigger.FortuneTrigger;\nimport de.erethon.dungeonsxl.trigger.ProgressTrigger;\nimport de.erethon.dungeonsxl.trigger.RedstoneTrigger;\nimport de.erethon.dungeonsxl.util.BlockUtilCompat;\nimport de.erethon.dungeonsxl.world.block.GameBlock;\nimport de.erethon.dungeonsxl.world.block.LockedDoor;\nimport de.erethon.dungeonsxl.world.block.MultiBlock;\nimport de.erethon.dungeonsxl.world.block.PlaceableBlock;\nimport de.erethon.dungeonsxl.world.block.RewardChest;\nimport de.erethon.dungeonsxl.world.block.TeamBed;\nimport de.erethon.dungeonsxl.world.block.TeamFlag;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.util.FileUtil;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.Hanging;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class DGameWorld extends DInstanceWorld implements GameWorld {\n\n    private XLib xlib;\n    private Game game;\n\n    private Type type = Type.DEFAULT;\n\n    private boolean isPlaying = false;\n    private boolean classes = false;\n\n    private Set<Block> placedBlocks = new HashSet<>();\n\n    private Set<GameBlock> gameBlocks = new HashSet<>();\n    private Set<LockedDoor> lockedDoors = new HashSet<>();\n    private Set<PlaceableBlock> placeableBlocks = new HashSet<>();\n    private Set<RewardChest> rewardChests = new HashSet<>();\n    private Set<TeamBed> teamBeds = new HashSet<>();\n    private Set<TeamFlag> teamFlags = new HashSet<>();\n\n    private List<ItemStack> secureObjects = new ArrayList<>();\n    private List<DungeonMob> mobs = new ArrayList<>();\n    private List<Trigger> triggers = new ArrayList<>();\n\n    private boolean readySign;\n\n    DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, Game game) {\n        super(plugin, resourceWorld, folder);\n        xlib = plugin.getXLib();\n        if (game == null) {\n            throw new IllegalArgumentException(\"Game must not be null\");\n        }\n        this.game = game;\n    }\n\n    @Override\n    public Type getType() {\n        return type;\n    }\n\n    @Override\n    public void setType(Type type) {\n        this.type = type;\n    }\n\n    @Override\n    public Game getGame() {\n        return game;\n    }\n\n    @Override\n    public Location getStartLocation(PlayerGroup dGroup) {\n        int index = getGame().getGroups().indexOf(dGroup);\n\n        // Try the matching location\n        StartSign anyStartSign = null;\n        for (DungeonSign sign : getDungeonSigns()) {\n            if (sign instanceof StartSign) {\n                anyStartSign = (StartSign) sign;\n                if (anyStartSign.getId() == index) {\n                    return anyStartSign.getTargetLocation();\n                }\n            }\n        }\n\n        // Try any start sign\n        if (anyStartSign != null) {\n            return anyStartSign.getTargetLocation();\n        }\n\n        // Lobby location as fallback\n        if (getLobbyLocation() != null) {\n            return getLobbyLocation();\n        }\n\n        return getWorld().getSpawnLocation();\n    }\n\n    @Override\n    public boolean areClassesEnabled() {\n        return classes;\n    }\n\n    @Override\n    public void setClassesEnabled(boolean enabled) {\n        classes = enabled;\n    }\n\n    @Override\n    public DungeonSign createDungeonSign(Sign sign, String[] lines) {\n        DungeonSign dSign = super.createDungeonSign(sign, lines);\n        if (dSign == null) {\n            return null;\n        }\n\n        LogicalExpression expression = null;\n        if (!dSign.isTriggerLineDisabled()) {\n            try {\n                expression = LogicalExpression.parse(lines[3]);\n            } catch (IllegalArgumentException exception) {\n                dSign.markAsErroneous(\"The trigger string \" + lines[3] + \" is invalid.\");\n            }\n        }\n        createTriggers(dSign, expression);\n        for (Trigger trigger : triggers) {\n            trigger.addListener(dSign);\n        }\n\n        if (dSign.isOnDungeonInit()) {\n            try {\n                dSign.initialize();\n            } catch (Exception exception) {\n                dSign.markAsErroneous(\"An error occurred while initializing a sign of the type \" + dSign.getName()\n                        + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n                exception.printStackTrace();\n            }\n            if (!dSign.isErroneous() && dSign.isSetToAir()) {\n                dSign.setToAir();\n            }\n        }\n\n        return dSign;\n    }\n\n    @Override\n    public Trigger createTrigger(TriggerListener owner, LogicalExpression expression) {\n        if (!expression.isAtomic()) {\n            throw new IllegalArgumentException(\"Expression is not atomic\");\n        }\n\n        String text = expression.getText();\n        if (text.isBlank()) {\n            return null;\n        }\n        char key = Character.toUpperCase(text.charAt(0));\n        String value;\n        if (plugin.getTriggerRegistry().containsKey(key)) {\n            value = text.substring(1, text.length() - 1);\n        } else {\n            key = 'T';\n            value = text;\n        }\n\n        Trigger trigger = getTrigger(key, value);\n        if (trigger != null) {\n            return trigger;\n        }\n\n        Class<? extends Trigger> clss = plugin.getTriggerRegistry().get(key);\n        if (clss == null) {\n            return null;\n        }\n\n        // Legacy shit\n        if (key == TriggerTypeKey.PROGRESS && value.matches(\"[0-99]/[0-999]\")) {\n            trigger = ProgressTrigger.getOrCreate(plugin, owner, expression, value);\n        } else {\n            trigger = Trigger.construct(key, plugin, owner, expression, value);\n        }\n        if (trigger == null) {\n            return null;\n        }\n\n        TriggerRegistrationEvent event = new TriggerRegistrationEvent(trigger);\n        Bukkit.getPluginManager().callEvent(event);\n        if (!event.isCancelled()) {\n            triggers.add(trigger);\n        }\n        return trigger;\n    }\n\n    @Override\n    public List<Trigger> createTriggers(TriggerListener owner, LogicalExpression expression) {\n        List<LogicalExpression> atomicExpressions = expression.getContents(true);\n        List<Trigger> created = new ArrayList<>(atomicExpressions.size());\n        for (LogicalExpression atomic : atomicExpressions) {\n            Trigger trigger = atomic.toTrigger(plugin, owner, true);\n            created.add(trigger);\n        }\n        return created;\n    }\n\n    public Trigger getTrigger(char key, String value) {\n        if (!Trigger.IDENTIFIABLE.contains(key)) {\n            return null;\n        }\n        for (Trigger trigger : triggers) {\n            if (trigger.getKey() != key) {\n                continue;\n            }\n            if (trigger.getValue().equalsIgnoreCase(value)) {\n                return trigger;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public Collection<Block> getPlacedBlocks() {\n        return placedBlocks;\n    }\n\n    /**\n     * @return the placeableBlocks\n     */\n    public Set<GameBlock> getGameBlocks() {\n        return gameBlocks;\n    }\n\n    /**\n     * @param gameBlock the gameBlock to add\n     */\n    public void addGameBlock(GameBlock gameBlock) {\n        gameBlocks.add(gameBlock);\n\n        if (gameBlock instanceof LockedDoor) {\n            lockedDoors.add((LockedDoor) gameBlock);\n        } else if (gameBlock instanceof PlaceableBlock) {\n            placeableBlocks.add((PlaceableBlock) gameBlock);\n        } else if (gameBlock instanceof RewardChest) {\n            rewardChests.add((RewardChest) gameBlock);\n        } else if (gameBlock instanceof TeamBed) {\n            teamBeds.add((TeamBed) gameBlock);\n        } else if (gameBlock instanceof TeamFlag) {\n            teamFlags.add((TeamFlag) gameBlock);\n        }\n    }\n\n    /**\n     * @param gameBlock the gameBlock to remove\n     */\n    public void removeGameBlock(GameBlock gameBlock) {\n        gameBlocks.remove(gameBlock);\n\n        if (gameBlock instanceof LockedDoor) {\n            lockedDoors.remove((LockedDoor) gameBlock);\n        } else if (gameBlock instanceof PlaceableBlock) {\n            placeableBlocks.remove((PlaceableBlock) gameBlock);\n        } else if (gameBlock instanceof RewardChest) {\n            rewardChests.remove((RewardChest) gameBlock);\n        } else if (gameBlock instanceof TeamBed) {\n            teamBeds.remove((TeamBed) gameBlock);\n        } else if (gameBlock instanceof TeamFlag) {\n            teamFlags.remove((TeamFlag) gameBlock);\n        }\n    }\n\n    /**\n     * @return the rewardChests\n     */\n    public Set<RewardChest> getRewardChests() {\n        return rewardChests;\n    }\n\n    /**\n     * @return the locked doors\n     */\n    public Set<LockedDoor> getLockedDoors() {\n        return lockedDoors;\n    }\n\n    /**\n     * @return the placeable blocks\n     */\n    public Set<PlaceableBlock> getPlaceableBlocks() {\n        return placeableBlocks;\n    }\n\n    /**\n     * @return the team beds\n     */\n    public Set<TeamBed> getTeamBeds() {\n        return teamBeds;\n    }\n\n    /**\n     * @return the team flags\n     */\n    public Set<TeamFlag> getTeamFlags() {\n        return teamFlags;\n    }\n\n    /**\n     * @return the secureObjects\n     */\n    public List<ItemStack> getSecureObjects() {\n        return secureObjects;\n    }\n\n    /**\n     * @param secureObjects the secureObjects to set\n     */\n    public void setSecureObjects(List<ItemStack> secureObjects) {\n        this.secureObjects = secureObjects;\n    }\n\n    @Override\n    public Collection<DungeonMob> getMobs() {\n        return mobs;\n    }\n\n    @Override\n    public void addMob(DungeonMob mob) {\n        mobs.add(mob);\n    }\n\n    @Override\n    public void removeMob(DungeonMob mob) {\n        mobs.remove(mob);\n    }\n\n    @Override\n    public boolean isPlaying() {\n        return isPlaying;\n    }\n\n    public void setPlaying(boolean isPlaying) {\n        this.isPlaying = isPlaying;\n    }\n\n    @Override\n    public Collection<Trigger> getTriggers() {\n        return triggers;\n    }\n\n    @Override\n    public Collection<Trigger> getTriggersFromKey(char key) {\n        return triggers.stream()\n                .filter(t -> t.getKey() == key)\n                .toList();\n    }\n\n    @Override\n    public boolean unregisterTrigger(Trigger trigger) {\n        return triggers.remove(trigger);\n    }\n\n    /**\n     * @return the potential amount of mobs in the world\n     */\n    public int getMobCount() {\n        int mobCount = 0;\n\n        signs:\n        for (DungeonSign sign : getDungeonSigns().toArray(DungeonSign[]::new)) {\n            if (!(sign instanceof MobSign)) {\n                continue;\n            }\n\n            for (Trigger trigger : sign.getTriggers()) {\n                if (trigger instanceof ProgressTrigger) {\n                    if (((ProgressTrigger) trigger).getFloorCount() > getGame().getFloorCount()) {\n                        break signs;\n                    }\n                }\n            }\n\n            mobCount += ((MobSign) sign).getInitialAmount();\n        }\n\n        return mobCount;\n    }\n\n    @Override\n    public Dungeon getDungeon() {\n        if (getGame() != null) {\n            return getGame().getDungeon();\n        }\n\n        for (Dungeon dungeon : plugin.getDungeonRegistry()) {\n            if (dungeon.containsFloor(getResource())) {\n                return dungeon;\n            }\n        }\n\n        return null;\n    }\n\n    public boolean hasReadySign() {\n        return readySign;\n    }\n\n    public void setReadySign(ReadySign readySign) {\n        this.readySign = readySign != null;\n    }\n\n    /**\n     * Set up the instance for the game\n     */\n    public void startGame() {\n        GameWorldStartGameEvent event = new GameWorldStartGameEvent(this, getGame());\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        getWorld().setDifficulty(getRules().getState(GameRule.DIFFICULTY));\n        Boolean doFireTick = getRules().getState(GameRule.FIRE_TICK);\n        if (Version.isAtLeast(Version.MC1_21_11)) {\n            getWorld().setGameRule(org.bukkit.GameRule.FIRE_SPREAD_RADIUS_AROUND_PLAYER, (doFireTick ? -1 : 0));\n        } else if (Version.isAtLeast(Version.MC1_13)) {\n            getWorld().setGameRule((org.bukkit.GameRule<Boolean>) org.bukkit.GameRule.getByName(\"DO_FIRE_TICK\"), doFireTick);\n        }\n\n        isPlaying = true;\n\n        for (DungeonSign sign : getDungeonSigns().toArray(new DungeonSign[getDungeonSigns().size()])) {\n            if (sign == null || sign.isOnDungeonInit()) {\n                continue;\n            }\n            try {\n                sign.initialize();\n            } catch (Exception exception) {\n                sign.markAsErroneous(\"An error occurred while initializing a sign of the type \" + sign.getName()\n                        + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n                exception.printStackTrace();\n            }\n            if (sign.isErroneous()) {\n                continue;\n            }\n            if (sign.isSetToAir()) {\n                sign.setToAir();\n            }\n            if (!sign.hasTriggers()) {\n                try {\n                    sign.trigger(null);\n                } catch (Exception exception) {\n                    sign.markAsErroneous(\"An error occurred while triggering a sign of the type \" + getName()\n                            + \". This is not a user error. Please report the following stacktrace to the developer of the plugin:\");\n                    exception.printStackTrace();\n                }\n            }\n        }\n\n        for (Trigger trigger : getTriggersFromKey(TriggerTypeKey.REDSTONE)) {\n            ((RedstoneTrigger) trigger).trigger(true, null);\n        }\n\n        for (Trigger trigger : getTriggersFromKey(TriggerTypeKey.FORTUNE)) {\n            ((FortuneTrigger) trigger).trigger(true, null);\n        }\n    }\n\n    /**\n     * Delete this instance.\n     */\n    @Override\n    public void delete() {\n        InstanceWorldUnloadEvent event = new InstanceWorldUnloadEvent(this);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return;\n        }\n\n        if (Bukkit.getPluginManager().getPlugin(\"Citizens\") != null) {\n            ((CitizensMobProvider) plugin.getExternalMobProviderRegistry().get(\"CI\")).removeSpawnedNPCs(getWorld());\n        }\n\n        kickAllPlayers();\n\n        getWorld().getEntities().forEach(Entity::remove);\n        String name = getWorld().getName();\n        boolean unloaded = Bukkit.unloadWorld(getWorld(), /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4));\n        if (unloaded) {\n            FileUtil.removeDir(getFolder());\n        } else {\n            MessageUtil.debug(plugin, \"Error: World could not be unloaded, players left in world: \" + !getWorld().getPlayers().isEmpty());\n        }\n        plugin.getInstanceCache().remove(this);\n        Bukkit.getPluginManager().callEvent(new InstanceWorldPostUnloadEvent(getResource(), name));\n    }\n\n    private GameRuleContainer getRules() {\n        return getDungeon().getRules();\n    }\n\n    /**\n     * Handles what happens when a player breaks a block.\n     *\n     * @param event the passed Bukkit event\n     * @return if the event is cancelled\n     */\n    public boolean onBreak(BlockBreakEvent event) {\n        Player player = event.getPlayer();\n        Block block = event.getBlock();\n        for (DungeonSign sign : getDungeonSigns()) {\n            if (sign == null) {\n                continue;\n            }\n            if ((block.equals(sign.getSign().getBlock()) || block.equals(BlockUtilCompat.getAttachedBlock(sign.getSign().getBlock()))) && sign.isProtected()) {\n                return true;\n            }\n        }\n\n        for (GameBlock gameBlock : gameBlocks) {\n            if (block.equals(gameBlock.getBlock())) {\n                if (gameBlock.onBreak(event)) {\n                    return true;\n                }\n\n            } else if (gameBlock instanceof MultiBlock) {\n                if (block.equals(((MultiBlock) gameBlock).getAttachedBlock())) {\n                    if (gameBlock.onBreak(event)) {\n                        return true;\n                    }\n                }\n            }\n        }\n\n        Game game = getGame();\n        if (game == null) {\n            return true;\n        }\n\n        BuildMode mode = getRules().getState(GameRule.BREAK_BLOCKS);\n\n        if (mode == BuildMode.FALSE) {\n            return true;\n        }\n\n        // Cancel if a protected entity is attached\n        for (Entity entity : getWorld().getNearbyEntities(block.getLocation(), 2, 2, 2)) {\n            if (!(entity instanceof Hanging)) {\n                continue;\n            }\n            if (entity.getLocation().getBlock().getRelative(((Hanging) entity).getAttachedFace()).equals(block)) {\n                Hanging hanging = (Hanging) entity;\n                if (getRules().getState(GameRule.DAMAGE_PROTECTED_ENTITIES).contains(xlib.getExMob(hanging))) {\n                    event.setCancelled(true);\n                    break;\n                }\n            }\n        }\n\n        boolean breakBlock = !mode.check(player, this, block);\n        if (breakBlock) {\n            placedBlocks.remove(block);\n        }\n        return breakBlock;\n    }\n\n    /**\n     * Handles what happens when a player places a block.\n     *\n     * @param player\n     * @param block\n     * @param against\n     * @param hand    the event parameters.\n     * @return if the event is cancelled\n     */\n    public boolean onPlace(Player player, Block block, Block against, ItemStack hand) {\n        Game game = getGame();\n        if (game == null) {\n            return true;\n        }\n\n        if (getRules().getState(GameRule.PLACE_BLOCKS).check(player, this, block)) {\n            placedBlocks.add(block);\n            return false;\n        }\n\n        PlaceableBlock placeableBlock = null;\n        for (PlaceableBlock gamePlaceableBlock : placeableBlocks) {\n            if (gamePlaceableBlock.canPlace(block, xlib.getExItem(hand))) {\n                placeableBlock = gamePlaceableBlock;\n                break;\n            }\n        }\n        if (placeableBlock == null) {\n            // Workaround for a bug that would allow 3-Block-high jumping\n            Location loc = player.getLocation();\n            if (loc.getY() > block.getY() + 1.0 && loc.getY() <= block.getY() + 1.5) {\n                if (loc.getX() >= block.getX() - 0.3 && loc.getX() <= block.getX() + 1.3) {\n                    if (loc.getZ() >= block.getZ() - 0.3 && loc.getZ() <= block.getZ() + 1.3) {\n                        loc.setX(block.getX() + 0.5);\n                        loc.setY(block.getY());\n                        loc.setZ(block.getZ() + 0.5);\n                        player.teleport(loc);\n                    }\n                }\n            }\n            return true;\n        }\n        placeableBlock.onPlace(player);\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/DInstanceWorld.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.player.InstancePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerCache;\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.compatibility.Version;\nimport java.io.File;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.bukkit.Bukkit;\nimport org.bukkit.Location;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.Sign;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class DInstanceWorld implements InstanceWorld {\n\n    protected DungeonsXL plugin;\n    protected PlayerCache dPlayers;\n\n    private static int counter;\n\n    protected Map<Block, DungeonSign> signs = new HashMap<>();\n    private DResourceWorld resourceWorld;\n    private File folder;\n    String world;\n    private int id;\n    private Location lobby;\n\n    DInstanceWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder) {\n        this.plugin = plugin;\n        dPlayers = plugin.getPlayerCache();\n\n        this.resourceWorld = resourceWorld;\n        this.folder = folder;\n        id = counter++;\n\n        plugin.getInstanceCache().add(id, this);\n    }\n\n    /* Getters and setters */\n    @Override\n    public String getName() {\n        return resourceWorld.getName();\n    }\n\n    @Override\n    public DResourceWorld getResource() {\n        return resourceWorld;\n    }\n\n    @Override\n    public File getFolder() {\n        return folder;\n    }\n\n    @Override\n    public World getWorld() {\n        if (world == null) {\n            return null;\n        }\n        return Bukkit.getWorld(world);\n    }\n\n    /**\n     * Returns false if this instance does not have a world, yet\n     *\n     * @return false if this instance does not have a world, yet\n     */\n    public boolean exists() {\n        return world != null;\n    }\n\n    @Override\n    public int getId() {\n        return id;\n    }\n\n    @Override\n    public Collection<DungeonSign> getDungeonSigns() {\n        return signs.values();\n    }\n\n    @Override\n    public DungeonSign createDungeonSign(Sign sign, String[] lines) {\n        String type = lines[0].substring(1, lines[0].length() - 1);\n        try {\n            Class<? extends DungeonSign> clss = plugin.getSignRegistry().get(type.toUpperCase());\n            if (clss == null) {\n                return null;\n            }\n            Constructor constructor = clss.getConstructor(DungeonsAPI.class, Sign.class, String[].class, InstanceWorld.class);\n            DungeonSign dSign = (DungeonSign) constructor.newInstance(plugin, sign, lines, this);\n            signs.put(sign.getBlock(), dSign);\n            return dSign;\n\n        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException\n                | IllegalArgumentException | InvocationTargetException exception) {\n            MessageUtil.log(plugin, \"&4Could not create a dungeon sign of the type \\\"\" + type\n                    + \"\\\". A dungeon sign implementation needs a constructor with the types (DungeonsAPI, org.bukkit.block.Sign, String[], InstanceWorld).\");\n            return null;\n        }\n    }\n\n    @Override\n    public void removeDungeonSign(DungeonSign sign) {\n        signs.remove(sign.getSign().getBlock());\n    }\n\n    @Override\n    public void removeDungeonSign(Block sign) {\n        signs.remove(sign);\n    }\n\n    @Override\n    public DungeonSign getDungeonSign(Block sign) {\n        return signs.get(sign);\n    }\n\n    @Override\n    public Location getLobbyLocation() {\n        return lobby;\n    }\n\n    @Override\n    public void setLobbyLocation(Location lobby) {\n        this.lobby = lobby;\n    }\n\n    @Override\n    public Collection<InstancePlayer> getPlayers() {\n        return plugin.getPlayerCache().getAllInstancePlayersIf(p -> p.getInstanceWorld() == this);\n    }\n\n    /* Actions */\n    @Override\n    public void sendMessage(String message) {\n        getPlayers().forEach(p -> MessageUtil.sendMessage(p.getPlayer(), message));\n    }\n\n    @Override\n    public void kickAllPlayers() {\n        getPlayers().forEach(p -> p.leave());\n        // Players who shouldn't be in the dungeon but still are for some reason\n        if (plugin.isLoaded() && world != null) {\n            getWorld().getPlayers().forEach(p -> p.teleport(Bukkit.getWorlds().get(0).getSpawnLocation()));\n        }\n    }\n\n    /**\n     * @param rules sets up the time and weather to match the rules\n     */\n    public void setWeather(GameRuleContainer rules) {\n        if (world == null || getWorld() == null) {\n            return;\n        }\n\n        if (rules.getState(GameRule.THUNDER) != null) {\n            if (rules.getState(GameRule.THUNDER)) {\n                getWorld().setThundering(true);\n                getWorld().setStorm(true);\n                getWorld().setThunderDuration(Integer.MAX_VALUE);\n            } else {\n                getWorld().setThundering(false);\n                getWorld().setStorm(false);\n            }\n        }\n\n        if (rules.getState(GameRule.TIME) != null) {\n            getWorld().setTime(rules.getState(GameRule.TIME));\n        }\n    }\n\n    /**\n     * @return a name for the instance\n     * @param game whether the instance is a GameWorld\n     */\n    public static String generateName(boolean game) {\n        String name = \"DXL_\" + (game ? \"Game\" : \"Edit\") + \"_\" + counter;\n        File instanceFolder = new File(Bukkit.getWorldContainer(), name);\n        while (instanceFolder.exists()) {\n            if (Bukkit.getWorld(name) != null) {\n                Bukkit.unloadWorld(name, /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4));\n            }\n            if (!instanceFolder.delete()) {\n                MessageUtil.log(DungeonsXL.getInstance(), \"&6Warning: An unrecognized junk instance (&4\" + name + \"&6) has been found, but could not be deleted.\");\n            }\n            counter++;\n            name = \"DXL_\" + (game ? \"Game\" : \"Edit\") + \"_\" + counter;\n            instanceFolder = new File(Bukkit.getWorldContainer(), name);\n        }\n        return name;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{name=\" + getName() + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/DResourceWorld.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.Dungeon;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.event.world.EditWorldGenerateEvent;\nimport de.erethon.dungeonsxl.api.event.world.ResourceWorldInstantiateEvent;\nimport de.erethon.dungeonsxl.api.player.EditPlayer;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.ResourceWorld;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.util.FileUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport org.bukkit.Bukkit;\nimport org.bukkit.GameRule;\nimport org.bukkit.OfflinePlayer;\nimport org.bukkit.World;\nimport org.bukkit.World.Environment;\nimport org.bukkit.WorldCreator;\nimport org.bukkit.WorldType;\n\n/**\n * @author Daniel Saukel\n */\npublic class DResourceWorld implements ResourceWorld {\n\n    public static final File RAW = new File(DungeonsXL.MAPS, \".raw\");\n\n    private DungeonsXL plugin;\n\n    private File folder;\n    private WorldConfig config;\n    private SignData signData;\n    EditWorld editWorld;\n\n    public DResourceWorld(DungeonsXL plugin, String name) {\n        this.plugin = plugin;\n\n        folder = new File(DungeonsXL.MAPS, name);\n        if (!folder.exists()) {\n            folder.mkdir();\n        }\n\n        File configFile = new File(folder, WorldConfig.FILE_NAME);\n        if (configFile.exists()) {\n            config = new WorldConfig(plugin, configFile);\n        }\n\n        signData = new SignData(new File(folder, SignData.FILE_NAME));\n    }\n\n    public DResourceWorld(DungeonsXL plugin, File folder) {\n        this.plugin = plugin;\n\n        this.folder = folder;\n\n        File configFile = new File(folder, WorldConfig.FILE_NAME);\n        if (configFile.exists()) {\n            config = new WorldConfig(plugin, configFile);\n        }\n\n        signData = new SignData(new File(folder, SignData.FILE_NAME));\n    }\n\n    /* Getters and setters */\n    @Override\n    public String getName() {\n        return folder.getName();\n    }\n\n    @Override\n    public void setName(String name) {\n        folder.renameTo(new File(folder.getParentFile(), name));\n        folder = new File(folder.getParentFile(), name);\n    }\n\n    @Override\n    public File getFolder() {\n        return folder;\n    }\n\n    @Override\n    public GameRuleContainer getRules() {\n        return getConfig(false);\n    }\n\n    /**\n     * Returns the config of this world.\n     *\n     * @param generate if a config should be generated if none exists\n     * @return the config of this world\n     */\n    public WorldConfig getConfig(boolean generate) {\n        if (config == null) {\n            File file = new File(folder, WorldConfig.FILE_NAME);\n            if (!file.exists() && generate) {\n                try {\n                    file.createNewFile();\n                } catch (IOException exception) {\n                    exception.printStackTrace();\n                }\n            }\n            config = new WorldConfig(plugin, file);\n        }\n\n        return config;\n    }\n\n    @Override\n    public Environment getWorldEnvironment() {\n        return (getConfig(false) != null && getConfig(false).getWorldEnvironment() != null) ? getConfig(false).getWorldEnvironment() : Environment.NORMAL;\n    }\n\n    @Override\n    public void addInvitedPlayer(OfflinePlayer player) {\n        getConfig(true).addInvitedPlayer(player.getUniqueId().toString());\n        config.save();\n    }\n\n    @Override\n    public boolean removeInvitedPlayer(OfflinePlayer player) {\n        if (config == null) {\n            return false;\n        }\n\n        config.removeInvitedPlayers(player.getUniqueId().toString(), player.getName().toLowerCase());\n        config.save();\n\n        EditPlayer editPlayer = plugin.getPlayerCache().getEditPlayer(player.getPlayer());\n        if (editPlayer != null) {\n            if (plugin.getEditWorld(editPlayer.getWorld()).getResource() == this) {\n                editPlayer.leave();\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    public boolean isInvitedPlayer(OfflinePlayer player) {\n        if (config == null) {\n            return false;\n        }\n\n        return config.getInvitedPlayers().contains(player.getName().toLowerCase()) || config.getInvitedPlayers().contains(player.getUniqueId().toString());\n    }\n\n    /* Actions */\n    @Override\n    public void backup() {\n        File target = new File(DungeonsXL.BACKUPS, getName() + \"-\" + System.currentTimeMillis());\n        FileUtil.copyDir(folder, target);\n    }\n\n    public DInstanceWorld instantiate(Game game) {\n        plugin.setLoadingWorld(true);\n        String name = DInstanceWorld.generateName(game != null);\n        File instanceFolder = new File(Bukkit.getWorldContainer(), name);\n\n        DInstanceWorld instance = game != null ? new DGameWorld(plugin, this, instanceFolder, game) : new DEditWorld(plugin, this, instanceFolder);\n        ResourceWorldInstantiateEvent event = new ResourceWorldInstantiateEvent(this, name);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return null;\n        }\n\n        FileUtil.copyDir(folder, instanceFolder, DungeonsXL.EXCLUDED_FILES);\n        instance.world = Bukkit.createWorld(WorldCreator.name(name).environment(getWorldEnvironment())).getName();\n        if (Version.isAtLeast(Version.MC1_21_11)) {\n            instance.getWorld().setGameRule(GameRule.FIRE_SPREAD_RADIUS_AROUND_PLAYER, 0);\n        } else if (Version.isAtLeast(Version.MC1_13)) {\n            instance.getWorld().setGameRule((GameRule<Boolean>) GameRule.getByName(\"DO_FIRE_TICK\"), false);\n        }\n        if (Bukkit.getPluginManager().isPluginEnabled(\"dynmap\")) {\n            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), \"dynmap pause all\");\n            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), \"dmap worldset \" + name + \" enabled:false\");\n            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), \"dynmap pause none\");\n        }\n\n        if (game != null) {\n            signData.deserializeSigns((DGameWorld) instance);\n            instance.getWorld().setAutoSave(false);\n        } else {\n            signData.deserializeSigns((DEditWorld) instance);\n        }\n\n        plugin.setLoadingWorld(false);\n        return instance;\n    }\n\n    @Override\n    public EditWorld getEditWorld() {\n        return editWorld;\n    }\n\n    @Override\n    public EditWorld getOrInstantiateEditWorld(boolean ignoreLimit) {\n        if (editWorld != null) {\n            return editWorld;\n        }\n        if (plugin.isLoadingWorld()) {\n            return null;\n        }\n        if (!ignoreLimit && plugin.getMainConfig().getMaxInstances() <= plugin.getInstanceCache().size()) {\n            return null;\n        }\n\n        editWorld = (EditWorld) instantiate(null);\n        return editWorld;\n    }\n\n    @Override\n    public GameWorld instantiateGameWorld(Game game, boolean ignoreLimit) {\n        if (plugin.isLoadingWorld()) {\n            return null;\n        }\n        if (!ignoreLimit && plugin.getMainConfig().getMaxInstances() <= plugin.getInstanceCache().size()) {\n            return null;\n        }\n        return (DGameWorld) instantiate(game);\n    }\n\n    @Override\n    public Dungeon getSingleFloorDungeon() {\n        return plugin.getDungeonRegistry().get(getName());\n    }\n\n    /**\n     * Returns the DXLData.data file\n     *\n     * @return the DXLData.data file\n     */\n    public SignData getSignData() {\n        return signData;\n    }\n\n    /**\n     * Generate a new DResourceWorld.\n     *\n     * @return the automatically created DEditWorld instance\n     */\n    public DEditWorld generate() {\n        String name = DInstanceWorld.generateName(false);\n        File folder = new File(Bukkit.getWorldContainer(), name);\n        WorldCreator creator = new WorldCreator(name);\n        creator.type(WorldType.FLAT);\n        creator.generateStructures(false);\n\n        DEditWorld editWorld = new DEditWorld(plugin, this, folder);\n        this.editWorld = editWorld;\n\n        ResourceWorldInstantiateEvent event = new ResourceWorldInstantiateEvent(this, name);\n        Bukkit.getPluginManager().callEvent(event);\n        if (event.isCancelled()) {\n            return null;\n        }\n\n        if (!RAW.exists()) {\n            createRaw();\n        }\n        FileUtil.copyDir(RAW, folder, DungeonsXL.EXCLUDED_FILES);\n        editWorld.generateIdFile();\n        editWorld.world = creator.createWorld().getName();\n        editWorld.generateIdFile();\n\n        Bukkit.getPluginManager().callEvent(new EditWorldGenerateEvent(editWorld));\n        return editWorld;\n    }\n\n    void clearFolder() {\n        for (File file : FileUtil.getFilesForFolder(getFolder())) {\n            if (file.getName().equals(SignData.FILE_NAME) || file.getName().equals(WorldConfig.FILE_NAME)) {\n                continue;\n            }\n            if (file.isDirectory()) {\n                FileUtil.removeDir(file);\n            } else {\n                file.delete();\n            }\n        }\n    }\n\n    /**\n     * Removes files that are not needed from a world\n     *\n     * @param dir the directory to purge\n     */\n    public static void deleteUnusedFiles(File dir) {\n        for (File file : dir.listFiles()) {\n            if (file.getName().equalsIgnoreCase(\"uid.dat\") || file.getName().contains(\".id_\")) {\n                file.delete();\n            }\n        }\n    }\n\n    /**\n     * Creates the \"raw\" world that is copied for new instances.\n     */\n    public static void createRaw() {\n        WorldCreator rawCreator = WorldCreator.name(\".raw\");\n        rawCreator.type(WorldType.FLAT);\n        rawCreator.generateStructures(false);\n        World world = rawCreator.createWorld();\n        File worldFolder = new File(Bukkit.getWorldContainer(), \".raw\");\n        FileUtil.copyDir(worldFolder, RAW, DungeonsXL.EXCLUDED_FILES);\n        Bukkit.unloadWorld(world, /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4));\n        FileUtil.removeDir(worldFolder);\n    }\n\n    @Override\n    public String toString() {\n        return \"DResourceWorld{name=\" + getName() + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/DWorldListener.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.dungeonsxl.api.world.EditWorld;\nimport de.erethon.dungeonsxl.api.world.GameWorld;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport de.erethon.dungeonsxl.player.DPlayerListener;\nimport de.erethon.dungeonsxl.util.ContainerAdapter;\nimport de.erethon.xlib.XLib;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.compatibility.Version;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.mob.ExMob;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport org.bukkit.World;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Entity;\nimport org.bukkit.entity.LivingEntity;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.Cancellable;\nimport org.bukkit.event.EventHandler;\nimport org.bukkit.event.EventPriority;\nimport org.bukkit.event.Listener;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.event.block.BlockFadeEvent;\nimport org.bukkit.event.block.BlockIgniteEvent;\nimport org.bukkit.event.block.BlockPlaceEvent;\nimport org.bukkit.event.block.BlockSpreadEvent;\nimport org.bukkit.event.entity.EntityDamageEvent;\nimport org.bukkit.event.entity.EntityExplodeEvent;\nimport org.bukkit.event.entity.ItemSpawnEvent;\nimport org.bukkit.event.hanging.HangingBreakEvent;\nimport org.bukkit.event.player.PlayerArmorStandManipulateEvent;\nimport org.bukkit.event.player.PlayerInteractEntityEvent;\nimport org.bukkit.event.player.PlayerInteractEvent;\nimport org.bukkit.event.weather.WeatherChangeEvent;\nimport org.bukkit.event.world.WorldInitEvent;\nimport org.bukkit.inventory.EquipmentSlot;\nimport org.bukkit.inventory.ItemStack;\nimport org.bukkit.inventory.PlayerInventory;\n\n/**\n * @author Daniel Saukel, Frank Baumann, Milan Albrecht\n */\npublic class DWorldListener implements Listener {\n\n    private DungeonsXL plugin;\n    private XLib xlib;\n\n    public DWorldListener(DungeonsXL plugin) {\n        this.plugin = plugin;\n        xlib = plugin.getXLib();\n    }\n\n    @EventHandler(priority = EventPriority.HIGHEST)\n    public void onInit(WorldInitEvent event) {\n        World world = event.getWorld();\n        if (plugin.isInstance(world)) {\n            world.setKeepSpawnInMemory(false);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockBreak(BlockBreakEvent event) {\n        Block block = event.getBlock();\n        // EditWorld Signs\n        EditWorld editWorld = plugin.getEditWorld(block.getWorld());\n        if (editWorld != null) {\n            editWorld.removeDungeonSign(event.getBlock());\n            return;\n        }\n\n        // Deny GameWorld block breaking\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(block.getWorld());\n        if (gameWorld != null) {\n            if (gameWorld.onBreak(event)) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockInteract(PlayerInteractEvent event) {\n        Player player = event.getPlayer();\n        if (DPlayerListener.isCitizensNPC(player)) {\n            return;\n        }\n        Block block = event.getClickedBlock();\n        if (block == null) {\n            return;\n        }\n        GameWorld gameWorld = plugin.getGameWorld(block.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        if (!gameWorld.isPlaying()) {\n            if (!plugin.getMainConfig().areLobbyContainersEnabled() || !ContainerAdapter.isValidContainer(block)) {\n                event.setCancelled(true);\n            }\n            return;\n        }\n\n        Map<ExItem, HashSet<ExItem>> blacklist = gameWorld.getDungeon().getRules().getState(GameRule.INTERACTION_BLACKLIST);\n        if (blacklist.isEmpty()) {\n            return;\n        }\n\n        ExItem material = VanillaItem.get(block.getType());\n        ExItem tool = xlib.getExItem(getItemInHand(event));\n        if (blacklist.containsKey(material)\n                && (blacklist.get(material) == null\n                || blacklist.get(material).isEmpty()\n                || blacklist.get(material).contains(tool))) {\n            event.setCancelled(true);\n        }\n    }\n\n    private ItemStack getItemInHand(PlayerInteractEvent event) {\n        PlayerInventory inventory = event.getPlayer().getInventory();\n        if (Version.isAtLeast(Version.MC1_9)) {\n            return event.getHand() == EquipmentSlot.HAND ? inventory.getItemInMainHand() : inventory.getItemInOffHand();\n        } else {\n            return inventory.getItemInHand();\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockPlace(BlockPlaceEvent event) {\n        Block block = event.getBlock();\n\n        DGameWorld gameWorld = (DGameWorld) plugin.getGameWorld(block.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        if (gameWorld.onPlace(event.getPlayer(), block, event.getBlockAgainst(), event.getItemInHand())) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockIgnite(BlockIgniteEvent event) {\n        if (!plugin.isInstance(event.getBlock().getWorld())) {\n            return;\n        }\n\n        if (event.getCause() != BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onBlockSpread(BlockSpreadEvent event) {\n        Block block = event.getSource();\n\n        if (plugin.isInstance(block.getWorld()) && VanillaItem.VINE.is(block)) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onEntityExplode(EntityExplodeEvent event) {\n        if (plugin.getGameWorld(event.getEntity().getWorld()) == null) {\n            return;\n        }\n        if (event.getEntity() instanceof LivingEntity) {\n            // Disable Creeper explosions in gameWorlds\n            event.setCancelled(true);\n        } else {// TODO respect block breaking game rules\n            // Disable drops from TNT\n            event.setYield(0);\n        }\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onEntityDamage(EntityDamageEvent event) {\n        onTouch(event, event.getEntity(), false);\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onHangingBreak(HangingBreakEvent event) {\n        onTouch(event, event.getEntity(), false);\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {\n        onTouch(event, event.getRightClicked(), true);\n    }\n\n    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)\n    public void onArmorStandManipulate(PlayerArmorStandManipulateEvent event) {\n        onTouch(event, event.getRightClicked(), true);\n    }\n\n    /**\n     * @param event    the event\n     * @param entity   the entity\n     * @param interact true = interact; false = break\n     */\n    public void onTouch(Cancellable event, Entity entity, boolean interact) {\n        GameWorld gameWorld = plugin.getGameWorld(entity.getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n        GameRuleContainer rules = gameWorld.getDungeon().getRules();\n        Set<ExMob> prot = interact ? rules.getState(GameRule.INTERACTION_PROTECTED_ENTITIES) : rules.getState(GameRule.DAMAGE_PROTECTED_ENTITIES);\n        if (prot.contains(xlib.getExMob(entity))) {\n            event.setCancelled(true);\n        }\n    }\n\n    // TODO: Is this necessary?\n    @EventHandler\n    public void onItemSpawn(ItemSpawnEvent event) {\n        if (plugin.getGameWorld(event.getLocation().getWorld()) != null) {\n            if (Category.SIGNS.containsItem(event.getEntity().getItemStack())) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n    @EventHandler\n    public void onBlockFade(BlockFadeEvent event) {\n        GameWorld gameWorld = plugin.getGameWorld(event.getBlock().getWorld());\n        if (gameWorld == null) {\n            return;\n        }\n\n        if (!gameWorld.isPlaying()) {\n            event.setCancelled(true);\n            return;\n        }\n\n        Set<ExItem> blockFadeDisabled = gameWorld.getGame().getRules().getState(GameRule.BLOCK_FADE_DISABLED);\n        if (blockFadeDisabled.isEmpty()) {\n            return;\n        }\n        if (gameWorld.getGame() != null && blockFadeDisabled.contains(VanillaItem.get(event.getBlock().getType()))) {\n            event.setCancelled(true);\n        }\n    }\n\n    @EventHandler\n    public void onWeatherChange(WeatherChangeEvent event) {\n        InstanceWorld instance = plugin.getInstanceWorld(event.getWorld());\n        if (instance instanceof EditWorld && event.toWeatherState()) {\n            event.setCancelled(true);\n        } else if (instance instanceof GameWorld) {\n            Boolean raining = ((GameWorld) instance).getDungeon().getRules().getState(GameRule.RAIN);\n            if (raining == null) {\n                return;\n            }\n            if ((raining && !event.toWeatherState()) || (!raining && event.toWeatherState())) {\n                event.setCancelled(true);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/LWCIntegration.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport com.griefcraft.lwc.LWC;\nimport com.griefcraft.scripting.JavaModule;\nimport com.griefcraft.scripting.event.LWCProtectionRegisterEvent;\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\n\n/**\n * @author Daniel Saukel\n */\npublic class LWCIntegration extends JavaModule {\n\n    private DungeonsXL plugin;\n\n    public LWCIntegration(DungeonsXL plugin) {\n        this.plugin = plugin;\n        LWC.getInstance().getModuleLoader().registerModule(plugin, this);\n    }\n\n    @Override\n    public void onRegisterProtection(LWCProtectionRegisterEvent event) {\n        InstanceWorld instance = plugin.getInstanceWorld(event.getBlock().getWorld());\n        if (instance != null) {\n            event.setCancelled(true);\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/SignData.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.api.sign.DungeonSign;\nimport de.erethon.dungeonsxl.api.world.InstanceWorld;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.Collection;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockState;\nimport org.bukkit.block.Sign;\n\n/**\n * Represents the data file of a dungeon map, mainly to store signs.\n *\n * @author Daniel Saukel\n */\npublic class SignData {\n\n    public static final String FILE_NAME = \"DXLData.data\";\n\n    private File file;\n\n    public SignData(File file) {\n        if (!file.exists()) {\n            try {\n                file.createNewFile();\n            } catch (IOException exception) {\n                exception.printStackTrace();\n            }\n        }\n\n        this.file = file;\n    }\n\n    public File getFile() {\n        return file;\n    }\n\n    public void updateFile(DResourceWorld resource) {\n        file = new File(resource.getFolder(), FILE_NAME);\n    }\n\n    /**\n     * Loads all signs from the file to the instance.\n     *\n     * @param instance the instance where the signs are\n     */\n    public void deserializeSigns(InstanceWorld instance) {\n        try {\n            ObjectInputStream os = new ObjectInputStream(new FileInputStream(file));\n\n            int length = os.readInt();\n            for (int i = 0; i < length; i++) {\n                int x = os.readInt();\n                int y = os.readInt();\n                int z = os.readInt();\n\n                Block block = instance.getWorld().getBlockAt(x, y, z);\n                BlockState state = block.getState();\n                if (state instanceof Sign) {\n                    Sign sign = (Sign) state;\n                    String[] lines = sign.getLines();\n                    instance.createDungeonSign(sign, lines);\n\n                    if (lines[0].equalsIgnoreCase(\"[lobby]\")) {\n                        instance.setLobbyLocation(block.getLocation());\n                    }\n                }\n            }\n\n            os.close();\n\n        } catch (IOException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n    /**\n     * Saves all signs from an instance to the file.\n     *\n     * @param instance the instance that contains the signs to serialize\n     */\n    public void serializeSigns(InstanceWorld instance) {\n        serializeSigns(instance.getDungeonSigns());\n    }\n\n    /**\n     * Saves all signs from the sign list to the file.\n     *\n     * @param signs the signs to serialize\n     */\n    public void serializeSigns(Collection<DungeonSign> signs) {\n        try {\n            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));\n            out.writeInt(signs.size());\n\n            for (DungeonSign sign : signs) {\n                out.writeInt(sign.getSign().getX());\n                out.writeInt(sign.getSign().getY());\n                out.writeInt(sign.getSign().getZ());\n            }\n\n            out.close();\n\n        } catch (IOException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/WorldConfig.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.dungeon.GameRule;\nimport de.erethon.dungeonsxl.api.dungeon.GameRuleContainer;\nimport de.erethon.xlib.util.EnumUtil;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.bukkit.World.Environment;\nimport org.bukkit.configuration.ConfigurationSection;\nimport org.bukkit.configuration.file.FileConfiguration;\nimport org.bukkit.configuration.file.YamlConfiguration;\n\n/**\n * The world configuration is a simple game rule source. Besides game rules, WorldConfig also stores some map specific data such as the invited players. It is\n * used directly in dungeon map config.yml files, but also part of dungeon and main config files.\n *\n * @author Frank Baumann, Milan Albrecht, Daniel Saukel\n */\npublic class WorldConfig extends GameRuleContainer {\n\n    private DungeonsXL plugin;\n\n    public static final String FILE_NAME = \"config.yml\";\n\n    private File file;\n    private ConfigurationSection config;\n\n    private List<String> invitedPlayers = new ArrayList<>();\n    private Environment worldEnvironment;\n\n    public WorldConfig(DungeonsXL plugin, File file) {\n        this.plugin = plugin;\n        this.file = file;\n        config = YamlConfiguration.loadConfiguration(file);\n        load();\n    }\n\n    public WorldConfig(DungeonsXL plugin, ConfigurationSection config) {\n        this.plugin = plugin;\n        if (config == null) {\n            config = new YamlConfiguration();\n        }\n        this.config = config;\n        load();\n    }\n\n    public void load() {\n        plugin.getGameRuleRegistry().forEach(this::updateGameRule);\n        invitedPlayers = config.getStringList(\"invitedPlayers\");\n        worldEnvironment = EnumUtil.getEnumIgnoreCase(Environment.class, config.getString(\"worldEnvironment\", Environment.NORMAL.name()));\n    }\n\n    public void updateGameRule(GameRule rule) {\n        rule.fromConfig(plugin, this, config);\n    }\n\n    public void save() {\n        if (file == null) {\n            return;\n        }\n        FileConfiguration configFile = YamlConfiguration.loadConfiguration(file);\n\n        if (getState(GameRule.MESSAGES) != null) {\n            for (int msgs : getState(GameRule.MESSAGES).keySet()) {\n                configFile.set(\"messages.\" + msgs, getState(GameRule.MESSAGES).get(msgs));\n            }\n        }\n\n        configFile.set(\"invitedPlayers\", invitedPlayers);\n        if (worldEnvironment != null) {\n            configFile.set(\"worldEnvironment\", worldEnvironment.name());\n        }\n\n        try {\n            configFile.save(file);\n\n        } catch (IOException exception) {\n            exception.printStackTrace();\n        }\n    }\n\n    /**\n     * @return the UUIDs or names of the players invited to edit the map\n     */\n    public List<String> getInvitedPlayers() {\n        return new ArrayList<>(invitedPlayers);\n    }\n\n    /**\n     * @param uuid the player's unique ID\n     */\n    public void addInvitedPlayer(String uuid) {\n        if (!invitedPlayers.contains(uuid)) {\n            invitedPlayers.add(uuid);\n        }\n    }\n\n    /**\n     * @param uuid the player's unique ID\n     * @param name the player's name\n     */\n    public void removeInvitedPlayers(String uuid, String name) {\n        invitedPlayers.remove(uuid);\n        invitedPlayers.remove(name);\n    }\n\n    /**\n     * @return the world environment\n     */\n    public Environment getWorldEnvironment() {\n        return worldEnvironment;\n    }\n\n    /**\n     * @param worldEnvironment the world environment to set\n     */\n    public void setWorldEnvironment(Environment worldEnvironment) {\n        this.worldEnvironment = worldEnvironment;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/GameBlock.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport org.bukkit.block.Block;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * A block that has a special purpose in a game.\n *\n * @author Daniel Saukel\n */\npublic abstract class GameBlock {\n\n    protected DungeonsAPI api;\n\n    protected Block block;\n\n    public GameBlock(DungeonsAPI api, Block block) {\n        this.api = api;\n        this.block = block;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the block\n     */\n    public Block getBlock() {\n        return block;\n    }\n\n    /**\n     * @param block the block to set\n     */\n    public void setBlock(Block block) {\n        this.block = block;\n    }\n\n    /* Abstracts */\n    /**\n     * Handles what happens when a player breaks the block.\n     *\n     * @param event the passed Bukkit event\n     * @return if the event is cancelled\n     */\n    public abstract boolean onBreak(BlockBreakEvent event);\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{block=\" + block + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/LockedDoor.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * A locked door that may be opened with a trigger.\n *\n * @author Daniel Saukel\n */\npublic class LockedDoor extends GameBlock implements MultiBlock {\n\n    private Block attachedBlock;\n\n    public LockedDoor(DungeonsAPI api, Block block) {\n        super(api, block);\n        attachedBlock = getAttachedBlock();\n    }\n\n    /* Getters and setters */\n    @Override\n    public Block getAttachedBlock() {\n        if (attachedBlock != null) {\n            return attachedBlock;\n\n        } else {\n            return block.getRelative(BlockFace.UP);\n        }\n    }\n\n    /* Actions */\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        return true;\n    }\n\n    /**\n     * Opens the door.\n     */\n    public void open() {\n        DungeonsXL.BLOCK_ADAPTER.openDoor(block);\n    }\n\n    /**\n     * Closes the door.\n     */\n    public void close() {\n        DungeonsXL.BLOCK_ADAPTER.closeDoor(block);\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{block=\" + block + \"; attachedBlock=\" + attachedBlock + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/MultiBlock.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport org.bukkit.block.Block;\n\n/**\n * In some cases, a \"game block\" might actually be a structure with multiple blocks, like a bed or a door with two halfs. These GameBlocks implement MultiBlock.\n *\n * @author Daniel Saukel\n */\npublic interface MultiBlock {\n\n    Block getAttachedBlock();\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/PlaceableBlock.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.trigger.SignTrigger;\nimport de.erethon.dungeonsxl.world.DGameWorld;\nimport de.erethon.xlib.item.ExItem;\nimport de.erethon.xlib.util.BlockUtil;\nimport de.erethon.xlib.util.NumberUtil;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class PlaceableBlock extends GameBlock {\n\n    private DGameWorld gameWorld;\n\n    private Set<ExItem> materials = new HashSet<>();\n    private Set<BlockFace> faces = new HashSet<>();\n    private int triggerId = -1;\n\n    public PlaceableBlock(DungeonsAPI api, DGameWorld gameWorld, Block block, String ids, String args) {\n        super(api, block);\n\n        this.gameWorld = gameWorld;\n\n        for (String id : ids.split(\",\")) {\n            ExItem item = api.getXLib().getExItem(id);\n            if (item != null) {\n                materials.add(item);\n            }\n        }\n\n        faces.add(BlockFace.SELF);\n        for (String arg : args.split(\",\")) {\n            int id = NumberUtil.parseInt(arg, -1);\n            if (id != -1) {\n                triggerId = id;\n            } else {\n                faces.add(BlockUtil.lettersToBlockFace(arg));\n            }\n        }\n    }\n\n    /* Actions */\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        return false;\n    }\n\n    public void onPlace(Player player) {\n        if (triggerId != -1) {\n            SignTrigger.getById(triggerId, gameWorld).trigger(true, player);\n        }\n        gameWorld.removeGameBlock(this);\n    }\n\n    public boolean canPlace(Block toPlace, ExItem material) {\n        return block.getX() == toPlace.getX() && block.getY() == toPlace.getY() && block.getZ() == toPlace.getZ()\n                && faces.contains(toPlace.getFace(block)) && (materials.isEmpty() || materials.contains(material));\n    }\n\n    public static boolean canBuildHere(Block block, ExItem material, DGameWorld gameWorld) {\n        for (PlaceableBlock gamePlaceableBlock : gameWorld.getPlaceableBlocks()) {\n            if (gamePlaceableBlock.canPlace(block, material)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/ProtectedBlock.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport org.bukkit.block.Block;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * @author Daniel Saukel\n */\npublic class ProtectedBlock extends GameBlock {\n\n    public ProtectedBlock(DungeonsAPI api, Block block) {\n        super(api, block);\n    }\n\n    /* Actions */\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/RewardChest.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.Reward;\nimport de.erethon.dungeonsxl.api.dungeon.Game;\nimport de.erethon.dungeonsxl.api.event.group.GroupCollectRewardEvent;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.api.player.PlayerGroup;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.reward.ItemReward;\nimport de.erethon.dungeonsxl.reward.LevelReward;\nimport de.erethon.dungeonsxl.reward.MoneyReward;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport de.erethon.xlib.util.SimpleDateUtil;\nimport net.milkbowl.vault.economy.Economy;\nimport org.bukkit.Bukkit;\nimport org.bukkit.ChatColor;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.BlockBreakEvent;\nimport org.bukkit.inventory.ItemStack;\n\n/**\n * @author Frank Baumann, Daniel Saukel\n */\npublic class RewardChest extends GameBlock {\n\n    private Economy econ;\n\n    private boolean used = false;\n    private double moneyReward;\n    private int levelReward;\n    private ItemStack[] itemReward;\n\n    public RewardChest(DungeonsXL plugin, Block container, double moneyReward, int levelReward, ItemStack[] itemReward) {\n        super(plugin, container);\n        econ = plugin.getXLib().getEconomyProvider();\n\n        this.moneyReward = moneyReward;\n        this.levelReward = levelReward;\n        this.itemReward = itemReward;\n    }\n\n    /**\n     * @return if the RewardChest is used\n     */\n    public boolean isUsed() {\n        return used;\n    }\n\n    /**\n     * @param used set if the chest is used\n     */\n    public void setUsed(boolean used) {\n        this.used = used;\n    }\n\n    /**\n     * @return the moneyReward\n     */\n    public double getMoneyReward() {\n        return moneyReward;\n    }\n\n    /**\n     * @param moneyReward the moneyReward to set\n     */\n    public void setMoneyReward(double moneyReward) {\n        this.moneyReward = moneyReward;\n    }\n\n    /**\n     * @return the levelReward\n     */\n    public double getLevelReward() {\n        return levelReward;\n    }\n\n    /**\n     * @param levelReward the levelReward to set\n     */\n    public void setLevelReward(int levelReward) {\n        this.levelReward = levelReward;\n    }\n\n    /* Actions */\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        return true;\n    }\n\n    /**\n     * @param opener the player who opens the chest\n     */\n    public void onOpen(Player opener) {\n        if (used) {\n            MessageUtil.sendMessage(Bukkit.getPlayer(opener.getUniqueId()), DMessage.ERROR_CHEST_IS_OPENED.getMessage());\n            return;\n        }\n\n        if (block.getLocation().distance(block.getLocation()) < 1) {\n            addTreasure(api.getPlayerGroup(opener), api.getPlayerCache().getGamePlayer(opener));\n            used = true;\n        }\n    }\n\n    public void addTreasure(PlayerGroup group, GamePlayer collector) {\n        if (group == null) {\n            return;\n        }\n        group.sendMessage(DMessage.GROUP_REWARD_CHEST.getMessage());\n\n        boolean hasMoneyReward = false;\n        boolean hasLevelReward = false;\n        boolean hasItemReward = false;\n\n        for (Reward reward : group.getRewards()) {\n            if (reward instanceof MoneyReward) {\n                hasMoneyReward = true;\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    ((MoneyReward) reward).addMoney(moneyReward);\n                }\n\n            } else if (reward instanceof LevelReward) {\n                hasLevelReward = true;\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    ((LevelReward) reward).addLevels(levelReward);\n                }\n\n            } else if (reward instanceof ItemReward) {\n                hasItemReward = true;\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    ((ItemReward) reward).addItems(itemReward);\n                }\n            }\n        }\n\n        Game game = group.getGame();\n        if (game == null || game.hasRewards()) {\n            if (!hasMoneyReward) {\n                MoneyReward reward = new MoneyReward(econ);\n                reward.addMoney(moneyReward);\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    group.getRewards().add(reward);\n                }\n            }\n\n            if (!hasLevelReward) {\n                LevelReward reward = new LevelReward();\n                reward.addLevels(levelReward);\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    group.getRewards().add(reward);\n                }\n            }\n\n            if (!hasItemReward) {\n                ItemReward reward = new ItemReward(api);\n                reward.addItems(itemReward);\n                GroupCollectRewardEvent event = new GroupCollectRewardEvent(group, collector, reward);\n                Bukkit.getPluginManager().callEvent(event);\n                if (!event.isCancelled()) {\n                    group.getRewards().add(reward);\n                }\n            }\n        }\n\n        for (Player player : group.getMembers().getOnlinePlayers()) {\n            DGamePlayer dPlayer = (DGamePlayer) api.getPlayerCache().getGamePlayer(player);\n            if (!dPlayer.canLoot(game.getDungeon())) {\n                MessageUtil.sendMessage(player, DMessage.ERROR_NO_REWARDS_TIME.getMessage(SimpleDateUtil.ddMMyyyyhhmm(dPlayer.getTimeNextLoot(game.getDungeon()))));\n                continue;\n            }\n\n            if (itemReward != null) {\n                String msg = \"\";\n                for (ItemStack itemStack : itemReward) {\n                    if (itemStack == null) {\n                        continue;\n                    }\n                    String name = null;\n                    if (itemStack.hasItemMeta()) {\n                        if (itemStack.getItemMeta().hasDisplayName()) {\n                            name = itemStack.getItemMeta().getDisplayName();\n                        }\n                    }\n                    if (name == null) {\n                        name = VanillaItem.get(itemStack.getType()).getName();\n                    }\n                    msg += ChatColor.RED + \" \" + itemStack.getAmount() + \" \" + name + ChatColor.GOLD + \",\";\n                }\n\n                if (msg.length() >= 1) {\n                    msg = msg.substring(0, msg.length() - 1);\n                }\n\n                MessageUtil.sendMessage(player, DMessage.PLAYER_LOOT_ADDED.getMessage(msg));\n            }\n\n            if (moneyReward != 0 && econ != null) {\n                MessageUtil.sendMessage(player, DMessage.PLAYER_LOOT_ADDED.getMessage(econ.format(moneyReward)));\n            }\n\n            if (levelReward != 0) {\n                MessageUtil.sendMessage(player, DMessage.PLAYER_LOOT_ADDED.getMessage(levelReward + \" levels\"));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/TeamBed.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGamePlayer;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.xlib.category.Category;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport org.bukkit.block.Block;\nimport org.bukkit.block.BlockFace;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * @author Daniel Saukel\n */\npublic class TeamBed extends TeamBlock implements MultiBlock {\n\n    private Block attachedBlock;\n\n    public TeamBed(DungeonsAPI api, Block block, DGroup owner) {\n        super(api, block, owner);\n        attachedBlock = getAttachedBlock();\n    }\n\n    /* Getters and setters */\n    public Block getAttachedBlock(Block block) {\n        if (Category.BEDS.containsBlock(block.getRelative(BlockFace.EAST))) {\n            return block.getRelative(BlockFace.EAST);\n\n        } else if (Category.BEDS.containsBlock(block.getRelative(BlockFace.NORTH))) {\n            return block.getRelative(BlockFace.NORTH);\n\n        } else if (Category.BEDS.containsBlock(block.getRelative(BlockFace.WEST))) {\n            return block.getRelative(BlockFace.WEST);\n\n        } else if (Category.BEDS.containsBlock(block.getRelative(BlockFace.SOUTH))) {\n            return block.getRelative(BlockFace.SOUTH);\n\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    public Block getAttachedBlock() {\n        if (attachedBlock != null) {\n            return attachedBlock;\n\n        } else {\n            return getAttachedBlock(block);\n        }\n    }\n\n    /* Actions */\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        Player breaker = event.getPlayer();\n        if (owner.getMembers().contains(breaker)) {\n            MessageUtil.sendMessage(breaker, DMessage.ERROR_BLOCK_OWN_TEAM.getMessage());\n            return true;\n        }\n\n        for (DGamePlayer player : owner.getDGamePlayers()) {\n            player.setLives(1);\n        }\n        owner.setLives(0);\n\n        owner.getGameWorld().sendMessage(DMessage.GROUP_BED_DESTROYED.getMessage(owner.getName(), api.getPlayerCache().getGamePlayer(breaker).getName()));\n        Block block1 = event.getBlock();\n        if (DungeonsXL.BLOCK_ADAPTER.isBedHead(block)) {\n            Block block2 = getAttachedBlock(block1);\n            if (block2 != null) {\n                block2.setType(VanillaItem.AIR.getMaterial());\n            }\n        }\n        block1.setType(VanillaItem.AIR.getMaterial());\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{block=\" + block + \"; attachedBlock=\" + attachedBlock + \"; owner=\" + owner + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/TeamBlock.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport org.bukkit.block.Block;\n\n/**\n * @author Daniel Saukel\n */\npublic abstract class TeamBlock extends GameBlock {\n\n    protected DGroup owner;\n\n    public TeamBlock(DungeonsAPI api, Block block, DGroup owner) {\n        super(api, block);\n        this.owner = owner;\n    }\n\n    /* Getters and setters */\n    /**\n     * @return the group that owns the flag\n     */\n    public DGroup getOwner() {\n        return owner;\n    }\n\n    /**\n     * @param owner the owner group to set\n     */\n    public void setOwner(DGroup owner) {\n        this.owner = owner;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \"{block=\" + block + \"; owner=\" + owner + \"}\";\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/java/de/erethon/dungeonsxl/world/block/TeamFlag.java",
    "content": "/*\n * Copyright (C) 2012-2013 Frank Baumann; 2015-2026 Daniel Saukel\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage de.erethon.dungeonsxl.world.block;\n\nimport de.erethon.dungeonsxl.DungeonsXL;\nimport de.erethon.dungeonsxl.api.DungeonsAPI;\nimport de.erethon.dungeonsxl.api.player.GamePlayer;\nimport de.erethon.dungeonsxl.config.DMessage;\nimport de.erethon.dungeonsxl.player.DGroup;\nimport de.erethon.xlib.chat.MessageUtil;\nimport de.erethon.xlib.item.VanillaItem;\nimport org.bukkit.block.Block;\nimport org.bukkit.entity.Player;\nimport org.bukkit.event.block.BlockBreakEvent;\n\n/**\n * @author Daniel Saukel\n */\npublic class TeamFlag extends TeamBlock {\n\n    public TeamFlag(DungeonsAPI api, Block block, DGroup owner) {\n        super(api, block, owner);\n        reset();\n    }\n\n    /* Actions */\n    /**\n     * Reset a team flag when the capturer dies.\n     */\n    public void reset() {\n        DungeonsXL.BLOCK_ADAPTER.setBlockWoolColor(block, owner.getDColor());\n    }\n\n    @Override\n    public boolean onBreak(BlockBreakEvent event) {\n        Player breaker = event.getPlayer();\n        GamePlayer gamePlayer = api.getPlayerCache().getGamePlayer(breaker);\n        if (gamePlayer == null) {\n            return true;\n        }\n\n        if (owner.getMembers().contains(breaker)) {\n            MessageUtil.sendMessage(breaker, DMessage.ERROR_BLOCK_OWN_TEAM.getMessage());\n            return true;\n        }\n\n        owner.getGameWorld().sendMessage(DMessage.GROUP_FLAG_STEALING.getMessage(gamePlayer.getName(), owner.getName()));\n        gamePlayer.setRobbedGroup(owner);\n        event.getBlock().setType(VanillaItem.AIR.getMaterial());\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "core/src/main/resources/languages/english.yml",
    "content": "announcer:\n  click: \"&4&l=> &6CLICK HERE TO JOIN &4&l<=\"\nbutton: \n  accept: \"&a[ YES ]\"\n  deny: \"&4[ NO ]\"\n  okay: \"&a[ OK ]\"\ncmd:\n  announce:\n    help: \"/dxl announce [name] - Runs an announcement script\"\n  break:\n    breakMode: \"&6You may break a block protected by DungeonsXL.\"\n    help: \"/dxl break - Break a block protected by DungeonsXL\"\n    protectedMode: \"&6You may not break blocks protected by DungeonsXL anymore.\"\n  chat:\n    dungeonChat: \"&6You have entered the dungeon chat\"\n    help: \"/dxl chat - Change the chat mode\"\n    normalChat: \"&6You are now in the public chat\"\n  chatspy:\n    help: \"/dxl chatspy - Dis/enables the spymode\"\n    started: \"&6You started to spy the dungeon chat.\"\n    stopped: \"&6You stopped to spy the dungeon chat.\"\n  create:\n    help: \"/dxl create [name] - Creates a new dungeon map\"\n  delete:\n    backups: \"&6Do you wish to delete all saved backups as well?\"\n    help: \"/dxl delete [name] - Deletes a dungeon map\"\n    success: \"&6Successfully deleted the map &4&v1&6.\"\n  dungeonItem:\n    dungeonItemHelp: \"&6After finishing a game, &4dungeon items &6are removed from the player's inventory even if the respective dungeon setup in general allows to keep it.\"\n    globalItemHelp: \"&6After finishing a game, &4global items &6can be taken out of dungeons if the respective dungeon setup in general allows to keep the inventory.\"\n    help: \"/dxl dungeonItem [true|false|info] - Sets the item stack in the player's hand to be one that cannot be taken out of a dungeon\"\n    info:\n      dungeon: \"&6This item is a &4dungeon item&6.\"\n      global: \"&6This item is a &4global item&6.\"\n    set:\n      dungeon: \"&6Successfully made this item a &4dungeon item&6.\"\n      global: \"&6Successfully made this item a &4global item&6.\"\n  edit:\n    help: \"/dxl edit [map] - Edit an existing dungeon map\"\n  enter:\n    help: \"/dxl enter ([joining group]) [target group] - Let the joining group enter the game of the target group\"\n    success: \"&6The group &4&v1 &6successfully entered the game of the group &4&v2&6.\"\n  escape:\n    help: \"/dxl escape - Leaves the current edit world without saving\"\n  game:\n    help: \"/dxl game - Shows information about the current game session\"\n  group:\n    help:\n      create: \"/dxl group create [group] - Creates a new group\"\n      disband: \"/dxl group disband ([group]) - Disbands a group\"\n      invite: \"/dxl group invite [player] - Invites someone to your group\"\n      join: \"/dxl group join [group]- Join a group\"\n      kick: \"/dxl group kick [player] - Kicks a player\"\n      main: \"/dxl group - Shows group command help\"\n      show: \"/dxl group show [group] - Shows a group\"\n      uninvite: \"/dxl group uninvite [player] - Takes back an invitation to your group\"\n  help:\n    help: \"/dxl help [page] - Shows the help page\"\n  import:\n    help: \"/dxl import [world] - Imports a world from the world container as a dungeon map\"\n    success: \"&6Successfully imported the world &4&v1&6.\"\n  invite:\n    help: \"/dxl invite [player] [dungeon] - Invite a player to edit a dungeon\"\n    success: \"&6Player &4&v1&6 was successfully invited to edit the map &4&v2&6.\"\n  join:\n    help: \"/dxl join [announcement] - Opens the GUI to join a group in an upcoming game\"\n  kick:\n    help: \"/dxl kick [player] - Kicks the player out of his group and dungeon\"\n    success: \"&6Successfully kicked &4&v1&6 out of the group.\"\n  leave:\n    help: \"/dxl leave - Leaves the current group and dungeon or edit world\"\n    success: \"&6You have successfully left your group.\"\n  list:\n    help: \"/dxl list ([dungeon|map|loaded]) ([dungeon]) - Lists all dungeons\"\n  lives:\n    group: \"&4&v1 &6have &4&v2 &6lives left.\"\n    help: \"/dxl lives [player] - Shows the lives a player has left\"\n    player: \"&4&v1 &6has &4&v2 &6lives left.\"\n  main:\n    compatibility: \"&eInternals: &o[&v1] &eVault: &o[&v2] &eXLib: &o[&v3]\"\n    help: \"/dxl - General status information\"\n    helpInfo: \"&7Type in &o/dxl help&r&7 for further information.\"\n    loaded: \"&eMaps: &o[&v1] &eDungeons: &o[&v2] &eLoaded: &o[&v3] &ePlayers: &o[&v4]\"\n    welcome: \"&7Welcome to &4Dungeons&fXL\"\n  msg:\n    added: \"&6New Messages (&4&v1&6) added.\"\n    help: \"/dxl msg [id] '[msg]' - Show or edit a message\"\n    updated: \"&6Messages (&4&v1&6) updated.\"\n  play:\n    help: \"/dxl play [name] - Allows the player to play a dungeon without a portal\"\n  portal:\n    help: \"/dxl portal ([material=portal]) - Creates a portal that leads into a dungeon\"\n  reload:\n    fail: \"&4You cannot reload right now.\"\n    help: \"/dxl reload - Reloads the plugin\"\n    players: \"&4Warning: If you reload the plugin, all players will be kicked out of their game.\"\n    success: \"&7Successfully reloaded DungeonsXL.\"\n  rename:\n    help: \"/dxl rename [old name] [new name] - Changes the name of a map to the new one. This command does NOT break dungeons that include this map.\"\n    success: \"&6Successfully renamed the map &4&v1&6 to &4&v2&6.\"\n  resourcePack:\n    help: \"/dxl resourcepack [ID] - Downloads a resourcepack registered in the main configuration file; use 'reset' to reset\"\n  save:\n    help: \"/dxl save - Saves the current dungeon\"\n    success: \"&6Map saved.\"\n  status:\n    help: \"/dxl status - Shows the technical status of DungeonsXL\"\n  test:\n    help: \"/dxl test [name] - Starts the game in test mode\"\n  uninvite:\n    help: \"/dxl uninvite [player] [map] - Uninvite a player from editing a map\"\n    success: \"&4&v1&6's permission to edit the map &4&v2&6 has been removed successfully.\"\ndayOfWeek:\n  0: \"Sunday\"\n  1: \"Monday\"\n  2: \"Tuesday\"\n  3: \"Wednesday\"\n  4: \"Thursday\"\n  5: \"Friday\"\n  6: \"Saturday\"\nerror:\n  bed: \"&4You cannot use a bed while in a dungeon.\"\n  blockOwnTeam: \"&4This block belongs to your own group.\"\n  chestIsOpened: \"&4This chest has already been opened.\"\n  cmd: \"&4Commands are not allowed while in a dungeon.\"\n  dispenser: \"&4You cannot access this dispenser.\"\n  drop: \"&4You cannot drop safe items\"\n  enderchest: \"&4You cannot use an enderchest while in a dungeon.\"\n  groupIsPlaying: \"&4This group is already in a dungeon.\"\n  inGroup: \"&4The player &6&v1&4 is already member of a group.\"\n  joinGroup: \"&4You have to join a group first.\"\n  leaveDungeon: \"&4You have to leave your current dungeon first.\"\n  leaveGame: \"&4You have to leave your current game first.\"\n  leaveGroup: \"&4You have to leave your group first.\"\n  msgFormat: \"&4Please use &6\\\" &4to mark the beginning and the end of the message.\"\n  msgIdDoesNotExist: \"&4Messages with Id &6&v1&4 does not exist.\"\n  msgNoInt: \"&4The argument [id] has to include a number.\"\n  nameInUse: \"&4The name &6&v1 &4is already in use.\"\n  nameTooLong: \"&4The name may not be longer than 15 characters.\"\n  noGame: \"&4You currently do not take part in a game.\"\n  noItemInMainHand: \"&4You do not have an item in your main hand.\"\n  noLeaveInTutorial: \"&4You cannot use this command in the tutorial.\"\n  noPermissions: \"&4You do not have permission to do this.\"\n  noProtectedBlock: \"&4This is not a block protected by DungeonsXL.\"\n  noReadySign: \"&4The world &6&v1 &4does not seem to have a ready sign. No game can be started and only lobby dungeon signs will be initialized.\"\n  noRewardsTime: \"&4You cannot receive rewards before &6&v1&4.\"\n  noSuchAnnouncement: \"&4This announcement does not exist.\"\n  noSuchDungeon: \"&4This dungeon does not exist.\"\n  noSuchGroup: \"&4The group &6&v1&4 does not exist.\"\n  noSuchMap: \"&4The world &6&v1&4 does not exist.\"\n  noSuchPlayer: \"&4The player &6&v1&4 does not exist.\"\n  noSuchResourcePack: \"&4The resource pack &6&v1 &4is not registered in the main configuration file.\"\n  noSuchShop: \"&4Shop &v1 &4not found...\"\n  notInDungeon: \"&4You are not in a dungeon.\"\n  notInGame: \"&4The group &6&v1&4 is not member of a game.\"\n  notInGroup: \"&4The player &6&v1&4 is not member of the group &6&v2&v4.\"\n  notInvited: \"&4You are not invited to the group &6&v1&4.\"\n  notLeader: \"&4You are not the captain of your group.\"\n  notSaved: \"&4The map &6&v1&4 has not been saved to the &6DungeonsXL/maps/ &4folder yet.\"\n  ready: \"&4Choose your class first.\"\n  requirements: \"&4You don't fulfill the requirements for this dungeon.\"\n  selfNotInGroup: \"&4You are not in any group.\"\n  signWrongFormat: \"&4The sign is not written correctly.\"\n  tooManyInstances: \"&4There are currently too many maps instantiated. Try it again in a few minutes.\"\n  tooManyTutorials: \"&4There are currently too many tutorials running. Try it again in a few minutes.\"\n  tutorialDoesNotExist: \"&4Tutorial dungeon does not exist.\"\ngroup:\n  bedDestroyed: \"&6The bed of the group &4&v1 &6has been destroyed by &4&v2&6.\"\n  congrats: \"&6Congratulations!\"\n  congratsSub: \"&l&4Your group &v1 &4won the match.\"\n  created: \"&4&v1&6 created the group &4&v2&6.\"\n  death: \"&4&v1 &6died. &4&v2 &6have &4&v3 &6lives left.\"\n  deathKick: \"&4&v1 &6was kicked because &4&v2 &6have no lives left.\"\n  defeated: \"&4The group &4v1 &6is defeated because it lost its last score point.\"\n  disbanded: \"&4&v1&6 disbanded the group &4&v2&6.\"\n  flagCaptured: \"&4&v1&6 has captured the flag of the group &4&v2&6.\"\n  flagLost: \"&4&v1&6 died and lost &4&v2&6's flag.\"\n  flagStealing: \"&4&v1&6 is stealing the flag of the group &4&v2&6.\"\n  invitedPlayer: \"&4&v1&6 invited the player &4&v2&6 to the group &4&v3&6.\"\n  joinedGame: \"&6Your group successfully joined the game.\"\n  kickedPlayer: \"&4&v1&6 kicked the player &4&v2&6 from the group &4&v3&6.\"\n  killed: \"&4&v1 &6was killed by &4&v2&6. &4&v3&6 have &4&v4 &6lives left.\"\n  killedKick: \"&4&v1&6 was killed by &4&v2&6. &4&v3 have no lives left.\"\n  livesAdded: \"&6Your group received a bonus of &4&v1&6 lives.\"\n  livesRemoved: \"&6Your group lost &4&v1&6 lives.\"\n  playerJoined: \"&6Player &4&v1&6 has joined the group.\"\n  rewardChest: \"&6Your group has found a reward chest.\"\n  uninvitedPlayer: \"&4&v1&6 took back the invitation for &4&v2&6 to the group &4&v3&6.\"\n  waveFinished: \"&6Your group finished wave no. &4&v1&6. The next one is going to start in &4&v2&6 seconds.\"\nplayer:\n  blockInfo: \"&6Block ID: &2&v1\"\n  checkpointReached: \"&6Checkpoint reached.\"\n  death: \"&4&v1 &6died and has &4&v2 &6lives left.\"\n  deathKick: \"&2&v1 &6lost his last life and was kicked.\"\n  finishedDungeon: \"&6You successfully finished the dungeon.\"\n  finished_Floor: \"&6You successfully finished the floor.\"\n  invited: \"&4&v1&6 invited you to the group &4&v2&6.\"\n  joinGroup: \"&6You successfully joined the group.\"\n  kicked: \"&4You have been kicked out of the group &6&v1&4.\"\n  killed: \"&4&v1 &6was killed by &4&v2 &6and has &4&v3 &6lives left.\"\n  killedKick: \"&4&v1&6 was killed by &4&v2 &6and lost his last life.\"\n  leaveGroup: \"&6You have successfully left your group.\"\n  leftGroup: \"&6Player &4&v1&6 has left the Group.\"\n  livesAdded: \"&6Received a bonus of &4&v1&6 lives.\"\n  livesRemoved: \"&6You lost &4&v1&6 lives.\"\n  lootAdded: \"&4&v1&6 have been added to your reward inventory.\"\n  newLeader: \"&6You are now the new captain of your group.\"\n  offline: \"&6Player &4&v1&6 went offline. In &4&v2&6 seconds he will autmatically be kicked from the dungeon.\"\n  offlineNever: \"&6The player &4&v1&6 went offline. He will &4not&6 be kicked from the dungeon automatically.\"\n  portal:\n    abort: \"&6Portal creation cancelled.\"\n    created: \"&6Portal created.\"\n    introduction: \"&6Click on the two edges of the portal with the wooden sword.\"\n    progress: \"&6First edge successfully marked. You may now click on the other edge.\"\n    rotate: \"&6Do you wish to rotate the portal?\"\n  protectedBlockDeleted: \"&6Successfully removed the protection.\"\n  ready: \"&6You are now ready to start the dungeon.\"\n  signCopied: \"&6Sign data copied.\"\n  signCreated: \"&6Successfully created a dungeon sign.\"\n  timeKick: \"&2&v1&6's time expired.\"\n  timeLeft: \"&v1You have &6&v2 &v1seconds left to finish the dungeon.\"\n  treasures: \"&1Treasures\"\n  uninvited: \"&4&v1&6 took back your invitation to the group &4&v2&6.\"\n  unlimitedLives: \"unlimited\"\n  waitForOtherPlayers: \"&6Waiting for team members...\"\nrequirement:\n  fee: \"&6You have been charged &4&v1 &6for entering the dungeon.\"\n  feeItems: \"Items\"\n  feeLevel: \"Levels\"\n  feeMoney: \"Money\"\n  finishedDungeons:\n    and: \"and\"\n    name: \"Finished dungeons\"\n    or: \"or\"\n    withinTime: \"within the last &v1 hours\"\n  forbiddenItems: \"Forbidden items\"\n  groupSize: \"Group size\"\n  keyItems: \"Keys\"\n  permission: \"Permissions\"\n  timeSince:\n    finish: \"Enough passed time since last playthrough (&v1 hours)\"\n    start: \"Enough passed time since last try (&v1 hours)\"\n    never: \"No playthroughs so far.\"\n  timeframe: \"Timeframe\"\nreward:\n  general: \"&6You received &4&v1 &6for finishing the dungeon.\"\nsign:\n  end: \"&aEND\"\n  floor:\n    1: \"&aENTER\"\n    2: \"&aNEXT FLOOR\"\n  global:\n    full: \"&4FULL\"\n    isPlaying: \"&4IS PLAYING\"\n    joinGame: \"&aJOIN GAME\"\n    joinGroup: \"&aJOIN GROUP\"\n    newGame: \"&aNEW GAME\"\n    newGroup: \"&aNEW GROUP\"\n  leave: \"&aLEAVE\"\n  ready: \"&aREADY\"\n  resourcePack: \"&aDOWNLOAD\"\n  wave:\n    1: \"&aSTART\"\n    2: \"&aNEXT WAVE\"\n"
  },
  {
    "path": "core/src/main/resources/languages/french.yml",
    "content": "announcer:\n  click: \"&4&l=> &6CLIQUEZ ICI POUR REJOINDRE &4&l<=\"\nbutton: \n  accept: \"&a[ OUI ]\"\n  deny: \"&4[ NON ]\"\n  okay: \"&a[ OK ]\"\ncmd:\n  announce:\n    help: \"/dxl announce [name] - Exécuter un script d'annonce\"\n  break:\n    breakMode: \"&6Vous pouvez casser un bloc protégé par DungeonsXL.\"\n    help: \"/dxl break - Casser un bloc protégé par DungeonsXL\"\n    protectedMode: \"&6Vous ne pouvez plus casser des blocs protégés par DungeonsXL.\"\n  chat:\n    dungeonChat: \"&6Vous êtes entrés dans l'espace de discussion du donjon.\"\n    help: \"/dxl chat - Changer le mode de discussion\"\n    normalChat: \"&6Vous êtes maintenant dans l'espace de discussion public.\"\n  chatspy:\n    help: \"/dxl chatspy - Activer/désactiver le mode espionnage\"\n    started: \"&6Vous avez commencé à espionner l'espace de discussion du donjon.\"\n    stopped: \"&6Vous avez arrêté d'espionner l'espace de discussion du donjon.\"\n  create:\n    help: \"/dxl create [name] - Créer une nouvelle carte de donjon\"\n  delete:\n    backups: \"&6Voulez-vous vraiment supprimer toutes les sauvegardes ?\"\n    help: \"/dxl create [name] - créé une nouvelle carte donjon\"\n    success: \"&6 &4&v1&6 a été supprimé avec succès.\"\n  dungeonItem:\n    dungeonItemHelp: \"&6Après avoir fini une partie, &4les objets du donjons &6sont supprimés de l'inventaire du joueur sauf si les paramètres du donjons autorisent à les conserver.\"\n    globalItemHelp: \"&6Après avoir fini une partie, &4les objets globaux &6peuvent être sortis du donjon si les paramètres du donjon l'autorise.\"\n    help: \"/dxl dungeonItem [true|false|info] - Défini la pile d'objet dans les mains du joueur comme ne pouvant être sortie du donjon.\"\n    info:\n      dungeon: \"&6C'est un &4objet de donjon&6.\"\n      global: \"&6C'est un &4objet global&6.\"\n    set:\n      dungeon: \"&6Vous avez réussi à en faire un &4objet de donjon&6.\"\n      global: \"&6Vous avez réussi à en faire un &4objet global&6.\"\n  edit:\n    help: \"/dxl edit [name] - Editer une carte existante de donjon\"\n  enter:\n    help: \"/dxl enter ([joining group]) [target group] - Laisser le groupe en train de rejoindre, entrer dans la partie du groupe cible\"\n    success: \"&6Le groupe &4&v1 &6est rentré avec succès dans la partie du groupe &4&v2&6.\"\n  escape:\n    help: \"/dxl escape - Quitter le monde actuel en cours d'édition sans sauvegarder\"\n  game:\n    help: \"/dxl game - Montrer les informations à propos de la partie de jeu actuel\"\n  group:\n    help:\n      create: \"/dxl group create [group] - Créer un nouveau groupe\"\n      disband: \"/dxl group disband ([group]) - Dissoudre un groupe\"\n      invite: \"/dxl group invite [player] - Inviter quelqu'un a votre groupe\"\n      join: \"/dxl group join [group]- Rejoindre un groupe\"\n      kick: \"/dxl group kick [player] - Éjecter un joueur\"\n      main: \"/dxl group - Montrer l'aide des commandes de groupe\"\n      show: \"/dxl group show [group] - Montrer un groupe\"\n      uninvite: \"/dxl group uninvite [player] - Retirer une invitation à votre groupe\"\n  help:\n    help: \"/dxl help [page] - Montrer la page d''aide\"\n  import:\n    help: \"/dxl import [world] - Importe un monde du conteneur de monde en tant que carte de donjon\"\n    success: \"&6Importation du monde &4&v1&6 avec succès.\"\n  invite:\n    help: \"/dxl invite [player] [dungeon] - Inviter un joueur à éditer un donjon\"\n    success: \"&6Le joueur &4&v1&6 a été invité avec succès à éditer la carte &4&v2&6.\"\n  join:\n    help: \"/dxl join [announcement] - Ouvrir l'interface pour rejoindre un groupe dans une prochaine partie\"\n  kick:\n    help: \"/dxl kick [player] - Éjecte le joueur hors de son groupe et du donjon en cours\"\n    success: \"&6Éjection de &4&v1&6 du groupe réussite.\"\n  leave:\n    help: \"/dxl leave - Quitter le groupe et le donjon ou le monde en cours d''édition\"\n    success: \"&6Vous avez quitté le groupe avec succès.\"\n  list:\n    help: \"/dxl list ([dungeon|map|loaded]) ([dungeon]) - Lister tous les donjons\"\n  lives:\n    group: \"&4&v1&6 ont &4&v2 &6vies restantes.\"\n    help: \"/dxl lives [player] - Montrer les vies d''un joueur venant de partir\"\n    player: \"&4&v1&6 a &4&v2 &6vies restantes.\"\n  main:\n    compatibility: \"&eInternals:&o[&v1] &eVault:&o[&v2] &eXLib:&o[&v3]\"\n    help: \"/dxl - Informations générales\"\n    helpInfo: \"&7Tapez dans &o/dxl help&r&7 pour plus d'information.\"\n    loaded: \"&eCartes:&o[&v1] &eDonjons:&o[&v2] &eChargés:&o[&v3] &eJoueurs:&o[&v4]\"\n    welcome: \"&7Bienvenue sur &4Dungeons&fXL\"\n  msg:\n    added: \"&6Nouveaux messages (&4&v1&6) ajoutés.\"\n    help: \"/dxl msg [id] '[msg]' - Montrer ou éditer un message\"\n    updated: \"&6Messages (&4&v1&6) mis à jour.\"\n  play:\n    help: \"/dxl play [name] - Permettre au joueur de jouer à un donjon sans un portail\"\n  portal:\n    help: \"/dxl portal ([material=portal]) - Créer un portail qui mène à un donjon\"\n  reload:\n    fail: \"&4Vous ne pouvez pas recharger maintenant.\"\n    help: \"/dxl reload - Recharge le plugin\"\n    players: \"&4Attention: Si vous rechargez le plugin, tous les joueurs seront éjectés de leur jeu.\"\n    success: \"&7DungeonsXL a été rechargé avec succès.\"\n  rename:\n    help: \"/dxl rename [old name] [new name] - Change le nom de la carte en un nouveau. Cette commande NE casse PAS les donjons comportant cette carte.\"\n    success: \"&6Carte &4&v1&6 renommée en &4&v2&6 avec succès.\"\n  resourcePack:\n    help: \"/dxl resourcepack [ID] - Télécharge un pack de ressources enregistré dans le fichier configuration principale; utilisez 'reset' pour réinitialisé\"\n  save:\n    help: \"/dxl save - Sauvegarder le donjon actuel\"\n    success: \"&6Carte sauvegardée.\"\n  status:\n    help: \"/dxl status - Affiche le status technique de DungeonsXL\"\n  test:\n    help: \"/dxl test [name] - Lance une partie en mode test\"\n  uninvite:\n    help: \"/dxl uninvite [player] [dungeon] - Désinvite un joueur à éditer un donjon\"\n    success: \"&4&v1&6 a été désinviter avec succès à éditer la carte &4&v1&6.\"\ndayOfWeek:\n  0: \"Dimanche\"\n  1: \"Lundi\"\n  2: \"Mardi\"\n  3: \"Mercredi\"\n  4: \"Jeudi\"\n  5: \"Vendredi\"\n  6: \"Samedi\"\nerror:\n  bed: \"&4Vous ne pouvez utiliser un lit en étant dans un donjon.\"\n  blockOwnTeam: \"&4Ce bloque appartient à votre groupe.\"\n  chestIsOpened: \"&4Ce coffre a déjà été ouvert.\"\n  cmd: \"&4Les commandes ne sont pas autorisées pendant un donjon.\"\n  dispenser: \"&4Vous ne pouvez accéder à ce distributeur.\"\n  drop: \"&4Vous ne pouvez pas lâcher cet objet protégé.\"\n  enderchest: \"&4Vous ne pouvez utiliser un coffre de l'ender quand vous êtes dans un donjon.\"\n  groupIsPlaying: \"&4Ce groupe est déjà dans un donjon.\"\n  inGroup: \"&4Le joueur &6&v1&4 est déjà membre d'un groupe.\"\n  joinGroup: \"&4Vous devez d'abord rejoindre un groupe.\"\n  leaveDungeon: \"&4Vous devez d'abord quitter votre donjon actuel.\"\n  leaveGame: \"&4Vous devez d'abord quitter votre partie actuelle.\"\n  leaveGroup: \"&4Vous devez d'abord quitter votre groupe.\"\n  msgFormat: \"&4Les messages doivent être entre \\\".\"\n  msgIdDoesNotExist: \"&4Le message avec  l'Id &6&v1&4 n'existe pas.\"\n  msgNoInt: \"&4L'argument [id] doit inclure un nombre.\"\n  nameInUse: \"&4Le nom &6&v1 &4est déjà utilisé.\"\n  nameTooLong: \"&4Le nom ne devrait pas être plus long que 15 caractères.\"\n  noGame: \"&4Vous ne prenez actuellement pas part à une partie.\"\n  noItemInMainHand: \"&4Vous n'avez pas d'objet dans votre main principale.\"\n  noLeaveInTutorial: \"&4Vous ne pouvez utiliser cette commande dans le tutoriel.\"\n  noPermissions: \"&4Vous n'avez pas la permission de faire ça.\"\n  noProtectedBlock: \"&4Ce n'est pas un bloc protégé par DungeonsXL.\"\n  noReadySign: \"&4La carte &6&v1 &4n'a pas de panneau \\\"ready\\\". On ne peut pas commencer des jeus et les panneaux de lobby ne sont que initialisés.\"\n  noRewardsTime: \"&4Vous ne pouvez pas recevoir de récompense avant &6&v1&4.\"\n  noSuchAnnouncement: \"&4Cette annonce n'existe pas.\"\n  noSuchDungeon: \"&4Le donjon &6&v1&4 n'existe pas.\"\n  noSuchGroup: \"&4Le groupe &6&v1&4 n'existe pas.\"\n  noSuchMap: \"&4La carte &6&v1&4 n'existe pas.\"\n  noSuchPlayer: \"&4Le joueur &6&v1&4 n'existe pas.\"\n  noSuchResourcePack: \"&4Le paque de ressources &6&v1 &4n'est pas enregistré dans le fichier de configuration principal.\"\n  noSuchShop: \"&4Shop &v1 &4not found...\"\n  notInDungeon: \"&4Vous n'êtes pas dans un donjon.\"\n  notInGame: \"&4Le groupe &6&v1&4 n'est pas membre d'une partie.\"\n  notInGroup: \"&4Le joueur &6&v1&4 n'est pas membre du groupe &6&v2&v4.\"\n  notInvited: \"&4Vous n'êtes pas invité au groupe &6&v1&4.\"\n  notLeader: \"&4Vous n'êtes pas le capitaine de votre groupe.\"\n  notSaved: \"&4La carte &6&v1&4 n'a pas encore été sauvegardée dans le dossier &6DungeonsXL/maps/&4.\"\n  ready: \"&4Choisissez d'abord votre classe.\"\n  requirements: \"&4Vous ne remplissez pas les prérequis pour ce donjon.\"\n  selfNotInGroup: \"Vous n'êtes dans aucun groupe.\"\n  signWrongFormat: \"&4Le panneau n'est pas rédigé correctement.\"\n  tooManyInstances: \"&4Il y a actuellement trop de carte instanciées. Réessayez dans quelques minutes.\"\n  tooManyTutorials: \"&4Il y a actuellement trop de tutoriel en cours. Réessayez dans quelques minutes.\"\n  tutorialDoesNotExist: \"&4Le tutoriel du donjon n'existe pas.\"\ngroup:\n  bedDestroyed: \"&6Le lit du groupe &4&v1 &6a été détruit par &4&v2&6.\"\n  congrats: \"&6Félicitations !\"\n  congratsSub: \"&l&4Votre groupe &v1 &4gagne le match.\"\n  created: \"&4&v1&6 a créé le groupe &4&v2&6.\"\n  death: \"&4&v1 &6est mort. &4&v2 &6a &4&v3 &6vies restantes.\"\n  deathKick: \"&4&v1 &6a été éjecté car &4&v2 &6n'a plus de vie restante.\"\n  defeated: \"&4Le groupe &4v1 &6a été défait car il a perdu son dernier point de score.\"\n  disbanded: \"&4&v1&6 a dissous le groupe &4&v2&6.\"\n  flagCaptured: \"&4&v1&6 a capturé le drapeau du groupe &4&v2&6.\"\n  flagLost: \"&4&v1&6 est mort et a perdu le drapeau de &4&v2&6.\"\n  flagStealing: \"&4&v1&6 is stealing the flag of the group &4&v2&6.\"\n  invitedPlayer: \"&4&v1&6 a invité le joueur &4&v2&6 au groupe &4&v3&6.\"\n  joinedGame: \"&6Votre groupe a rejoint la partie avec succès.\"\n  kickedPlayer: \"&4&v1&6 a éjecté &4&v2&6 du groupe &4&v3&6.\"\n  killed: \"&4&v1 &6a été tué par &4&v2&6. &4&v3&6 a &4&v4 &6vies restantes.\"\n  killedKick: \"&4&v1&6 a été tué par &4&v2&6. &4&v3 n'a plus de vies restantes.\"\n  livesAdded: \"&6Votre groupe a reçu un bonus de &4&v1&6 vies.\"\n  livesRemoved: \"&6Votre groupe a perdu &4&v1&6 vies.\"\n  playerJoined: \"&6Le joueur &4&v1&6 a rejoint le groupe.\"\n  rewardChest: \"&6Votre groupe a trouvé un coffre de récompenses.\"\n  uninvitedPlayer: \"&4&v1&6 a repris l'invitation de &4&v2&6 pour le groupe &4&v3&6.\"\n  waveFinished: \"&6Votre groupe a fini la vague numéro &4&v1&6. La suivante va commencer dans &4&v2&6 secondes.\"\nplayer:\n  blockInfo: \"&6Block ID:&2&v1\"\n  checkpointReached: \"&6Point de sauvegarde atteint.\"\n  death: \"&4&v1 &6est mort, vies restantes:&4&v2\"\n  deathKick: \"&2&v1&6 est mort et a perdu sa dernière vie.\"\n  finishedDungeon: \"&6Vous avez terminé le donjon avec succès.\"\n  finished_Floor: \"&6Vous avez terminé l'étage avec succès.\"\n  invited: \"&4&v1&6 vous a invité au groupe &4&v2&6.\"\n  joinGroup: \"&6Vous avez rejoint le groupe avec succès.\"\n  kicked: \"&4Vous avez été éjecté du groupe &6&v1&4.\"\n  killed: \"&4&v1 &6a été tué par &4&v2 &6et a &4&v3 &6vies restantes.\"\n  killedKick: \"&4&v1&6 a été tué par &4&v2 &6et a perdu sa dernière vie.\"\n  leaveGroup: \"&6Vous avez quitté le groupe avec succès.\"\n  leftGroup: \"&6Le joueur &4&v1&6 a quitté le groupe.\"\n  livesAdded: \"&6Received a bonus of &4&v1&6 lives.\"\n  livesRemoved: \"&6Vous perdez &4&v1&6 vies.\"\n  lootAdded: \"&4&v1&6 a été rajouté à votre inventaire de récompenses.\"\n  newLeader: \"&6Vous êtes maintenant le nouveau capitaine du groupe.\"\n  offline: \"&6Le joueur &4&v1&6 est passé hors ligne. Dans &4&v2&6 secondes il va être automatiquement éjecté du donjon.\"\n  offlineNever: \"&6Le joueur &4&v1&6 est passé hors ligne. Il ne va &4pas&6 être éjecté du donjon automatiquement.\"\n  portal:\n    abort: \"&6Création de portails annulée.\"\n    created: \"&6Portail créé.\"\n    introduction: \"&6Cliquez sur les deux bords du portail avec l'épée en bois.\"\n    progress: \"&6Le premier bord a été marqué avec succès. Vous devez maintenant cliquer sur le deuxième bord.\"\n    rotate: \"&6Voulez-vous tourner le portail ?\"\n  protectedBlockDeleted: \"&6Protection retirée avec succès.\"\n  ready: \"&6Vous êtes maintenant prêt à démarrer le donjon.\"\n  signCopied: \"&6Données du panneau copiées.\"\n  signCreated: \"&6Création avec succès d'un panneau de donjon.\"\n  timeKick: \"Temps de &2&v1&6 écoulé.\"\n  timeLeft: \"&v1Il vous reste &6&v2 &v1secondes pour finir le donjon.\"\n  treasures: \"&1trésors\"\n  uninvited: \"&4&v1&6 a annulé votre invitation au groupe &4&v2&6.\"\n  unlimitedLives: \"illimité\"\n  waitForOtherPlayers: \"&6En attente des membres de l'équipe...\"\nrequirement:\n  fee: \"&6Vous avez été débité de &4&v1 &6pour entrer dans le donjon.\"\n  feeItems: \"Items\"\n  feeLevel: \"Niveaux\"\n  feeMoney: \"Argent\"\n  finishedDungeons:\n    and: \"et\"\n    name: \"Donjons complétés\"\n    or: \"ou\"\n    withinTime: \"au cours des &v1 dernières heurs\"\n  forbiddenItems: \"Objets défendus\"\n  groupSize: \"Quantité des joueurs\"\n  keyItems: \"Clés\"\n  permission: \"Permissions\"\n  timeSince:\n    finish: \"Assez de temps passé depuis le dernier jeu terminé (&v1 heures)\"\n    start: \"Assez de temps passé depuis le dernier jeu commencé (&v1 heures)\"\n    never: \"Pas de jeux.\"\n  timeframe: \"Créneau horaire\"\nreward:\n  general: \"&6Vous avez reçu &4&v1 &6pour avoir fini le donjon.\"\nsign:\n  end: \"&aFINIR\"\n  floor:\n    1: \"&aENTRER AU PRO-\"\n    2: \"&aCHAIN ÉTAGE\"\n  global:\n    full: \"&4PLEIN\"\n    isPlaying: \"&4EN JEU\"\n    joinGame: \"&aINSCRIRE AU JEU\"\n    joinGroup: \"&aINSCRIRE AU GROUPE\"\n    newGame: \"&aNOUVEAU JEU\"\n    newGroup: \"&aNOUVEAU GROUPE\"\n  leave: \"&aQUITTER\"\n  ready: \"&aPRÊT\"\n  resourcePack: \"&aTÉLÉCHARGER\"\n  wave:\n    1: \"&aCOMMENCER\"\n    2: \"&aVAGUE SUIVANTE\"\n"
  },
  {
    "path": "core/src/main/resources/languages/german.yml",
    "content": "announcer:\n  click: \"&4&l=> &6KLICKE HIER ZUM BEITRETEN &4&l<=\"\nbutton:\n  accept: \"&a[ JA ]\"\n  deny: \"&4[ NEIN ]\"\n  okay: \"&a[ OK ]\"\ncmd:\n  announce:\n    help: \"/dxl announce [name] - Startet ein Announcer-Script\"\n  break:\n    breakMode: \"&6Du darfst jetzt von DungeonsXL geschützte Blöcke zerstören.\"\n    help: \"/dxl break - Zerstöre einen von DungeonsXL geschützten Block\"\n    protectedMode: \"&6Du darfst keine von DungeonsXL geschützten Blöcke mehr zerstören.\"\n  chat:\n    dungeonChat: \"&6Du hast den Dungeon-Chat betreten.\"\n    help: \"/dxl chat - Chat-Modus wechseln\"\n    normalChat: \"&6Du bist jetzt im öffentlichen Chat.\"\n  chatspy:\n    help: \"/dxl chatspy - (De-)Aktiviert den Chat-Spy\"\n    started: \"&6Du liest jetzt den Dungeon-Chat mit.\"\n    stopped: \"&6Du hast aufgehört, den Dungeon-Chat mitzulesen.\"\n  create:\n    help: \"/dxl create [name] - Erstellt eine neue Dungeon-Map\"\n  delete:\n    backups: \"&6Möchtest Du auch alle gespeicherten Backups löschen?\"\n    help: \"/dxl delete [name] - Löscht eine Dungeon-Map\"\n    success: \"&6Die Welt &4&v1&6 wurde erfolgreich gelöscht.\"\n  dungeonItem:\n    dungeonItemHelp: \"&6Nachdem ein Spiel beendet wurde, werden &4Dungeon-Items &6selbst dann aus dem Inventar entfernt, wenn die Spielregeln das eigentlich erlauben.\"\n    globalItemHelp: \"&6Nachdem ein Spiel beendet wurde, können &4globale Items &6aus den Dungeons mit herausgenommen werden, wenn die Spielregeln das erlauben.\"\n    help: \"/dxl dungeonItem [true|false|info] - Stellt den Item-Stack in der Hand des Spielers so ein, dass er nicht aus dem Dungeon mit herausgenommen werden kann\"\n    info:\n      dungeon: \"&6Dieses Item ist ein &4Dungeon-Item&6.\"\n      global: \"&6Dieses Item ist ein &4globales Item&6.\"\n    set:\n      dungeon: \"&6Das Item wurde erfolgreich als &4Dungeon-Item&6 eingestellt.\"\n      global: \"&6Das Item wurde erfolgreich als &4globales Item&6 eingestellt.\"\n  edit:\n    help: \"/dxl edit [map] - Eine vorhandene Welt bearbeiten\"\n  enter:\n    help: \"/dxl enter ([joining group]) [target group] - Lässt eine Gruppe das Spiel einer anderen betreten.\"\n    success: \"&6Die Gruppe &4&v1 &6ist erfolgreich dem Spiel der Gruppe &4&v2&6 beigetreten.\"\n  escape:\n    help: \"/dxl escape - Die Welt ohne zu speichern verlassen\"\n  game:\n    help: \"/dxl game - Zeigt Informationen über das laufende Spiel an\"\n  group:\n    help:\n      create: \"/dxl group create [group] - Erstellt eine neue Gruppe\"\n      disband: \"/dxl group disband ([group]) - Löst eine Gruppe auf\"\n      invite: \"/dxl group invite [player] - Lädt jemanden in die Gruppe ein\"\n      join: \"/dxl group join [group]- Einer Gruppe beitreten\"\n      kick: \"/dxl group kick [player] - Einen Spieler aus einer Gruppe herauswerfen\"\n      main: \"/dxl group - Zeigt die Hilfeseiten des group-Befehls\"\n      show: \"/dxl group show [group] - Zeigt eine Gruppe an\"\n      uninvite: \"/dxl group uninvite [player] - Nimmt eine Einladung für die Gruppe zurück\"\n  help:\n    help: \"/dxl help [page] - Zeigt die Hilfeseite\"\n  import:\n    help: \"/dxl import [world] - Importiert eine Welt aus dem World-Container als Dungeon-Map\"\n    success: \"&6Die Welt &4&v1&6 wurde erfolgreich importiert.\"\n  invite:\n    help: \"/dxl invite [player] [dungeon] - Lädt einen Spieler ein, eine Dungeon-Welt zu bearbeiten\"\n    success: \"&6Der Spieler &4&v1&6 wurde erfolgreich dazu eingeladen, die Welt &4&v2&6 zu bearbeiten.\"\n  join:\n    help: \"/dxl join [announcement] - Öffnet ein GUI, um einer Gruppe in einem zukünftigen Spiel beizutreten\"\n  kick:\n    help: \"/dxl kick [player] - Wirft den Spieler aus seiner Gruppe und damit aus dem Dungeon heraus\"\n    success: \"&4&v1&6 wurde erfolgreich aus der Gruppe geworfen.\"\n  leave:\n    help: \"/dxl leave - Die Gruppe und Instanz verlassen\"\n    success: \"&6Du hast Deine Gruppe erfolgreich verlassen.\"\n  list:\n    help: \"/dxl list ([dungeon|map|loaded]) ([dungeon]) - Listet alle Dungeons auf\"\n  lives:\n    group: \"&4&v1 &6haben &4&v2 &6Leben übrig.\"\n    help: \"/dxl lives [player] - Zeigt die Leben, die ein Spieler übrig hat\"\n    player: \"&4&v1 &6hat &4&v2 &6Leben übrig.\"\n  main:\n    compatibility: \"&eInternals:&o[&v1] &eVault:&o[&v2] &eXLib:&o[&v3]\"\n    help: \"/dxl - Grundlegende Statusinformationen\"\n    helpInfo: \"&7Gib &o/dxl help&r&7 für weitere Informationen ein.\"\n    loaded: \"&eWelten:&o[&v1] &eDungeons:&o[&v2] &eGeladen:&o[&v3] &eSpieler:&o[&v4]\"\n    welcome: \"&7Willkommen bei &4Dungeons&fXL\"\n  msg:\n    added: \"&6Neue Nachrichten (&4&v1&6) hinzugefügt.\"\n    help: \"/dxl msg [id] '[msg]' - Zeigt oder bearbeitet eine Nachricht\"\n    updated: \"&6Nachrichten (&4&v1&6) aktualisiert.\"\n  play:\n    help: \"/dxl play [name] - Erlaubt dem Spieler, einen Dungeon ohne Portal zu spielen\"\n  portal:\n    help: \"/dxl portal ([material=portal]) - Erstellt ein Portal, das in einen Dungeon führt\"\n  reload:\n    fail: \"&4Du kannst gerade nicht neu laden.\"\n    help: \"/dxl reload - Lädt das Plugin neu\"\n    players: \"&4Warnung: Wenn Du das Plugin neu lädst, werden alle Spieler aus ihrem Spiel geworfen.\"\n    success: \"&7DungeonsXL wurde erfolgreich neu geladen.\"\n  rename:\n    help: \"/dxl rename [old name] [new name] - Ändert den Namen des Dungeons in einen neuen. Dieser Befehl updatet auch Namensreferenzen zu dem Dungeon\"\n    success: \"&4&v1&6 wurde erfolgreich zu &4&v2&6 umbenannt.\"\n  resourcePack:\n    help: \"/dxl resourcepack [ID] - Downloadet ein in der Haupt-Config registriertes Ressourcenpaket; nutze 'reset' um es zurückzusetzen\"\n  save:\n    help: \"/dxl save - Speichert den Dungeon\"\n    success: \"&6Welt gespeichert.\"\n  status:\n    help: \"/dxl status - Zeigt den technischen Status von DungeonsXL\"\n  test:\n    help: \"/dxl test [name] - Startet das Spiel im Testmodus\"\n  uninvite:\n    help: \"/dxl uninvite [player] [map] - Lädt einen Spieler davon aus, eine Welt zu bearbeiten\"\n    success: \"&4&v1&6's Berechtigung zum Bearbeiten der Welt &4&v2&6 wurde erfolgreich entfernt.\"\ndayOfWeek:\n  0: \"Sonntag\"\n  1: \"Montag\"\n  2: \"Dienstag\"\n  3: \"Mittwoch\"\n  4: \"Donnerstag\"\n  5: \"Freitag\"\n  6: \"Samstag\"\nerror:\n  bed: \"&4Du kannst im Dungeon kein Bett benutzen.\"\n  blockOwnTeam: \"&4Dieser Block gehört zu Deiner eigenen Gruppe.\"\n  chestIsOpened: \"&4Diese Truhe wurde schon geöffnet.\"\n  cmd: \"&4Befehle sind im Dungeon nicht erlaubt.\"\n  dispenser: \"&4Du kannst auf diesen Werfer nicht zugreifen.\"\n  drop: \"&4Du kannst keine gesicherten Items wegwerfen.\"\n  enderchest: \"&4Du kannst im Dungeon keine Endertruhen benutzen.\"\n  groupIsPlaying: \"&4Diese Gruppe ist schon in einem Dungeon.\"\n  inGroup: \"&4Der Spieler &6&v1&4 ist schon Mitglied einer Gruppe.\"\n  joinGroup: \"&4Du musst zuerst einer Gruppe beitreten.\"\n  leaveDungeon: \"&4Du musst zuerst Deinen Dungeon verlassen.\"\n  leaveGame: \"&4Du musst zuerst Dein Spiel verlassen.\"\n  leaveGroup: \"&4Du musst zuerst Deine Gruppe verlassen.\"\n  msgFormat: \"&4Bitte benutze &6\\\"&4-Zeichen um den Anfang und das Ende der Nachricht zu markieren.\"\n  msgIdDoesNotExist: \"&4Eine Nachricht mit der ID &6&v1&4 gibt es nicht.\"\n  msgNoInt: \"&4Das Argument [id] muss numerisch sein.\"\n  nameInUse: \"&4Der Name &6&v1 &4wird bereits verwendet.\"\n  nameTooLong: \"&4Der Name darf nicht länger als 15 Zeichen sein.\"\n  noGame: \"&4Du nimmst momentan nicht an einem Spiel teil.\"\n  noItemInMainHand: \"&4Du hast kein Item in Deiner Haupthand.\"\n  noLeaveInTutorial: \"&4Du kannst diesen Befehl im Tutorial nicht benutzen.\"\n  noPermissions: \"&4Du hast nicht die nötigen Berechtigungen, um das zu tun.\"\n  noProtectedBlock: \"&4Das ist kein von DungeonsXL geschützter Block.\"\n  noReadySign: \"&4Die Welt &6&v1 &4hat kein Ready-Schild. Spiele können nicht begonnen werden und nur Lobby-Schilder werden geladen.\"\n  noRewardsTime: \"&4Du kannst vor &6&v1&4 keine Belohnungen bekommen.\"\n  noSuchAnnouncement: \"&4Dieser Announcer existiert nicht.\"\n  noSuchDungeon: \"&4Dieser Dungeon existiert nicht.\"\n  noSuchGroup: \"&4Die Gruppe &6&v1&4 gibt es nicht.\"\n  noSuchMap: \"&4Die Welt &6&v1&4 gibt es nicht.\"\n  noSuchPlayer: \"&4Den Spieler &6&v1&4 gibt es nicht.\"\n  noSuchResourcePack: \"&4Das Ressourcenpaket &6&v1 &4ist in der Hauptkonfigurationsdatei nicht registriert.\"\n  noSuchShop: \"&4Der Shop &v1 &4wurde nicht gefunden...\"\n  notInDungeon: \"&4Du bist nicht in einem Dungeon.\"\n  notInGame: \"&4Die Gruppe &6&v1&4 ist kein Mitglied des Spiels.\"\n  notInGroup: \"&4Der Spieler &6&v1&4 ist kein Mitglied der Gruppe &6&v2&v4.\"\n  notInvited: \"&4Du wurdest in die Gruppe &6&v1&4 nicht eingeladen.\"\n  notLeader: \"&4Du bist nicht der Kapitän der Gruppe.\"\n  notSaved: \"&4Die Welt &6&v1&4 wurde noch nicht im &6DungeonsXL/maps/&4-Ordner abgespeichert.\"\n  ready: \"&4Wähle erst Deine Klasse.\"\n  requirements: \"&4Du erfüllst nicht alle Voraussetzungen, um diesen Dungeon zu spielen.\"\n  selfNotInGroup: \"&4Du bist in keiner Gruppe.\"\n  signWrongFormat: \"&4Das Schild ist nicht richtig beschriftet.\"\n  tooManyInstances: \"&4Im Moment sind zu viele Welten instanziert. Versuche es in ein paar Minuten nochmal.\"\n  tooManyTutorials: \"&4Im Moment laufen zu viele Tutorials. Versuche es in ein paar Minuten nochmal.\"\n  tutorialDoesNotExist: \"&4Der Tutorial-Dungeon existiert nicht.\"\ngroup:\n  bedDestroyed: \"&6Das Bett der Gruppe &4&v1 &6wurde von &4&v2&6 zerstört.\"\n  congrats: \"&6Herzlichen Glückwunsch!\"\n  congratsSub: \"&l&4Deine Gruppe &v1 &4hat das Spiel gewonnen.\"\n  created: \"&4&v1&6 hat die Gruppe &4&v2&6 erstellt.\"\n  death: \"&4&v1 &6ist gestorben. &4&v2 &6haben jetzt &4&v3 &6Leben übrig.\"\n  deathKick: \"&4&v1 &6wurde gekickt, weil &4&v2 &6keine Leben mehr übrig haben.\"\n  defeated: \"&4Die Gruppe &4v1 &6wurde besiegt, weil sie ihren letzten Punkt verloren hat.\"\n  disbanded: \"&4&v1&6 hat die Gruppe &4&v2&6 aufgelöst.\"\n  flagCaptured: \"&4&v1&6 hat die Flagge der Gruppe &4&v2&6 gestohlen.\"\n  flagLost: \"&4&v1&6 ist gestorben und hat die Flagge der Gruppe &4&v2&6 verloren.\"\n  flagStealing: \"&4&v1&6 stiehlt die Flagge der Gruppe &4&v2&6.\"\n  invitedPlayer: \"&4&v1&6 hat den Spieler &4&v2&6 in die Gruppe &4&v3&6 eingeladen.\"\n  joinedGame: \"&6Deine Gruppe ist dem Spiel erfolgreich beigetreten.\"\n  kickedPlayer: \"&4&v1&6 hat den Spieler &4&v2&6 aus der Gruppe &4&v3&6 herausgeworfen.\"\n  killed: \"&4&v1 &6wurde von &4&v2&6 getötet. &4&v3&6 haben jetzt &4&v4 &6Leben übrig.\"\n  killedKick: \"&4&v1&6 wurde von &4&v2&6 getötet. &4&v3 haben keine Leben mehr übrig.\"\n  livesAdded: \"&6Deine Gruppe hat &4&v1&6 Extraleben bekommen.\"\n  livesRemoved: \"&6Deine Gruppe hat &4&v1&6 Leben verloren.\"\n  playerJoined: \"&6Der Spieler &4&v1&6 hat die Gruppe betreten.\"\n  rewardChest: \"&6Deine Gruppe hat eine Schatztruhe gefunden.\"\n  uninvitedPlayer: \"&4&v1&6 hat die Einladung für &4&v2&6 in die Gruppe &4&v3&6 zurückgezogen.\"\n  waveFinished: \"&6Deine Gruppe hat die &4&v1&6te Welle beendet. Die nächste fängt in &4&v2&6 Sekunden an.\"\nplayer:\n  blockInfo: \"&6Block-ID:&2&v1\"\n  checkpointReached: \"&6Checkpoint erreicht.\"\n  death: \"&4&v1 &6ist gestorben und hat &4&v2 &6Leben übrig.\"\n  deathKick: \"&2&v1 &6hat sein letztes Leben verloren und wurde aus dem Spiel geworfen.\"\n  finishedDungeon: \"&6Du hast den Dungeon erfolgreich beendet.\"\n  finished_Floor: \"&6Du hast das Level erfolgreich beendet.\"\n  invited: \"&4&v1&6 hat Dich eingeladen, der Gruppe &4&v2&6 beizutreten.\"\n  joinGroup: \"&6Du bist der Gruppe erfolgreich beigetreten.\"\n  kicked: \"&4Du wurdest aus der Gruppe &6&v1&4 herausgeworfen.\"\n  killed: \"&4&v1 &6wurde von &4&v2 &6getötet und hat &4&v3 &6Leben übrig.\"\n  killedKick: \"&4&v1&6 wurde von &4&v2 &6getötet und hat das letzte Leben verloren.\"\n  leaveGroup: \"&6Du hast die Gruppe erfolgreich verlassen.\"\n  leftGroup: \"&6Der Spieler &4&v1&6 hat die Gruppe verlassen.\"\n  livesAdded: \"&6Du hast &4&v1&6 Extraleben bekommen.\"\n  livesRemoved: \"&6Du hast &4&v1&6 Leben verloren.\"\n  lootAdded: \"&4&v1&6 wurden Deinem Belohnungsinventar hinzugefügt.\"\n  newLeader: \"&6Du bist jetzt der neue Kapitän Deiner Gruppe.\"\n  offline: \"&6Der Spieler &4&v1&6 hat den Server verlassen. In &4&v2&6 Sekunden wird er automatisch aus dem Spiel geworfen.\"\n  offlineNever: \"&6Der Spieler &4&v1&6 hat den Server verlassen. Er wird aber &4nicht&6 automatisch aus dem Spiel geworfen.\"\n  portal:\n    abort: \"&6Das Erstellen des Portals wurde abgebrochen.\"\n    created: \"&6Das Portal wurde erstellt.\"\n    introduction: \"&6Klicke die zwei Ecken des Portals mit dem Holzschwert an.\"\n    progress: \"&6Das erste Eck wurde erfolgreich markiert. Klicke nun das zweite Eck an.\"\n    rotate: \"&6Möchtest Du das Portal drehen?\"\n  protectedBlockDeleted: \"&6Die Sicherung wurde erfolgreich entfernt.\"\n  ready: \"&6Du bist jetzt bereit, das Spiel zu starten.\"\n  signCopied: \"&6Schilddaten kopiert.\"\n  signCreated: \"&6Das Dungeon-Schild wurde erfolgreich erstellt.\"\n  timeKick: \"&6Die Zeit von &2&v1&6 ist abgelaufen.\"\n  timeLeft: \"&v1Du hast &6&v2 &v1Sekunden übrig, um den Dungeon zu beenden.\"\n  treasures: \"&1Schätze\"\n  uninvited: \"&4&v1&6 hat Deine Einladung zur Gruppe &4&v2&6 zurückgezogen.\"\n  unlimitedLives: \"unendlich viele\"\n  waitForOtherPlayers: \"&6Warte auf andere Gruppenmitglieder...\"\nrequirement:\n  fee: \"&6Du hast &4&v1 &6bezahlt, um den Dungeon zu betreten.\"\n  feeItems: \"Items\"\n  feeLevel: \"Level\"\n  feeMoney: \"Geld\"\n  finishedDungeons:\n    and: \"und\"\n    name: \"Abgeschlossene Dungeons\"\n    or: \"oder\"\n    withinTime: \"innerhalb der letzten &v1 Stunden\"\n  forbiddenItems: \"Verbotene Items\"\n  groupSize: \"Gruppengröße\"\n  keyItems: \"Schlüssel\"\n  permission: \"Permissions\"\n  timeSince:\n    finish: \"Ausreichend vergangene Zeit seit dem letzten abgeschlossenen Spiel (&v1 Stunden)\"\n    start: \"Ausreichend vergangene Zeit seit dem letzten Versuch (&v1 Stunden)\"\n    never: \"Bisher keine Versuche.\"\n  timeframe: \"Zeitfenster\"\nreward:\n  general: \"&6Du hast &4&v1 &6für das Beenden des Dungeons erhalten.\"\nsign:\n  end: \"&aENDE\"\n  floor:\n    1: \"&aNÄCHSTES LEVEL\"\n    2: \"&aBETRETEN\"\n  global:\n    full: \"&4VOLL\"\n    isPlaying: \"&4AM SPIELEN\"\n    joinGame: \"&aSPIEL BEITRETEN\"\n    joinGroup: \"&aGRUPPE BEITRETEN\"\n    newGame: \"&aNEUES SPIEL\"\n    newGroup: \"&aNEUE GRUPPE\"\n  leave: \"&aVERLASSEN\"\n  ready: \"&aBEREIT\"\n  resourcePack: \"&aDOWNLOAD\"\n  wave:\n    1: \"&aBEGINNE\"\n    2: \"&aNÄCHSTE WELLE\"\n"
  },
  {
    "path": "core/src/main/resources/plugin.yml",
    "content": "name: ${project.parent.name}\nmain: de.erethon.dungeonsxl.DungeonsXL\nversion: ${project.parent.version}${buildNo}\nauthors: [Frank Baumann, Milan Albrecht, Tobias Schmitz, Daniel Saukel]\ndescription: ${project.parent.description}\nwebsite: ${project.parent.url}\ndepend: [XLib-Runtime]\nsoftdepend: [CommandsXL, Vault, Citizens, CustomMobs, InsaneMobs, MythicMobs, HolographicDisplays, LWC, PlaceholderAPI, Parties]\ncommands:\n  dungeonsxl:\n    description: Reference command for DungeonsXL.\n    aliases: [dxl,dungeon]\napi-version: 1.13\n"
  },
  {
    "path": "dist/pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-dist</artifactId>\n    <version>${project.parent.version}</version>\n    <packaging>jar</packaging>\n    <parent>\n        <groupId>de.erethon.dungeonsxl</groupId>\n        <artifactId>dungeonsxl-parent</artifactId>\n        <version>0.19-SNAPSHOT</version>\n    </parent>\n    <build>\n        <finalName>${project.artifactId}-${project.version}${buildNo}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.6.1</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <artifactSet>\n                                <includes>\n                                    <include>de.erethon.dungeonsxl:*</include>\n                                </includes>\n                            </artifactSet>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-adapter</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-api</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-bukkit_blockdata</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-bukkit_magicvalues</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>de.erethon.dungeonsxl</groupId>\n            <artifactId>dungeonsxl-core</artifactId>\n            <version>${project.parent.version}</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>de.erethon.dungeonsxl</groupId>\n    <artifactId>dungeonsxl-parent</artifactId>\n    <version>0.19-SNAPSHOT</version>\n    <packaging>pom</packaging>\n    <name>DungeonsXL</name>\n    <url>https://dre2n.github.io</url>\n    <description>Create custom dungeons and adventure maps with ease!</description>\n    <modules>\n        <module>adapter</module>\n        <module>api</module>\n        <module>bukkit_blockdata</module>\n        <module>bukkit_magicvalues</module>\n        <module>core</module>\n        <module>dist</module>\n        <!--<module>addon</module>-->\n    </modules>\n    <properties>\n        <buildNo></buildNo>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <spigotVersion.latest>26.1.2-R0.1-SNAPSHOT</spigotVersion.latest>\n        <dependencyVersion.xlib>7.0-SNAPSHOT</dependencyVersion.xlib>\n    </properties>\n    <dependencies>\n        <dependency>\n            <groupId>de.erethon.xlib</groupId>\n            <artifactId>xlib-api</artifactId>\n            <version>${dependencyVersion.xlib}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n    <repositories>\n        <repository>\n            <id>spigot-repo</id>\n            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>\n        </repository>\n        <repository>\n            <id>dre-repo</id>\n            <url>https://erethon.de/repo</url>\n        </repository>\n    </repositories>\n</project>\n"
  }
]