Compare commits
26 Commits
e55e5d60cc
...
1.21.8-1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 49f5c0d139 | |||
| f2321145f5 | |||
| 4edb062b2a | |||
| ab8f821f8b | |||
| b74e5d2c48 | |||
| d63a899fb8 | |||
| daf1311bb3 | |||
| b24431cc55 | |||
| 85c7453e30 | |||
| 335b1596af | |||
| 7f9c20ab7d | |||
| 3000585586 | |||
| 07f940caf1 | |||
| 8163dabf2d | |||
| 999e5fb752 | |||
| b8cdeb343a | |||
| 31ea4af347 | |||
| 04f58b17da | |||
| e6c74ecded | |||
| 0e058e284e | |||
| 3e5da14735 | |||
| 188e515972 | |||
| aabd595816 | |||
| 3053a86d1b | |||
| d4a19ef7c3 | |||
| 1a8841ce5a |
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# gradle
|
||||||
|
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
classes/
|
||||||
|
|
||||||
|
# eclipse
|
||||||
|
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# idea
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
|
||||||
|
.settings/
|
||||||
|
.vscode/
|
||||||
|
bin/
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
|
# macos
|
||||||
|
|
||||||
|
*.DS_Store
|
||||||
|
|
||||||
|
# fabric
|
||||||
|
|
||||||
|
run/
|
||||||
|
.tmp-mc/
|
||||||
|
|
||||||
|
# java
|
||||||
|
|
||||||
|
hs_err_*.log
|
||||||
|
replay_*.log
|
||||||
|
*.hprof
|
||||||
|
*.jfr
|
||||||
573
LICENSE
573
LICENSE
@@ -1,373 +1,202 @@
|
|||||||
Mozilla Public License Version 2.0
|
|
||||||
==================================
|
|
||||||
|
|
||||||
1. Definitions
|
Apache License
|
||||||
--------------
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
1.1. "Contributor"
|
|
||||||
means each individual or legal entity that creates, contributes to
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
the creation of, or owns Covered Software.
|
|
||||||
|
1. Definitions.
|
||||||
1.2. "Contributor Version"
|
|
||||||
means the combination of the Contributions of others (if any) used
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
by a Contributor and that particular Contributor's Contribution.
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
1.3. "Contribution"
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
means Covered Software of a particular Contributor.
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
1.4. "Covered Software"
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
means Source Code Form to which the initial Contributor has attached
|
other entities that control, are controlled by, or are under common
|
||||||
the notice in Exhibit A, the Executable Form of such Source Code
|
control with that entity. For the purposes of this definition,
|
||||||
Form, and Modifications of such Source Code Form, in each case
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
including portions thereof.
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
means
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
(a) that the initial Contributor has attached the notice described
|
exercising permissions granted by this License.
|
||||||
in Exhibit B to the Covered Software; or
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
(b) that the Covered Software was made available under the terms of
|
including but not limited to software source code, documentation
|
||||||
version 1.1 or earlier of the License, but not also under the
|
source, and configuration files.
|
||||||
terms of a Secondary License.
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
1.6. "Executable Form"
|
transformation or translation of a Source form, including but
|
||||||
means any form of the work other than Source Code Form.
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
1.7. "Larger Work"
|
|
||||||
means a work that combines Covered Software with other material, in
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
a separate file or files, that is not Covered Software.
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
1.8. "License"
|
(an example is provided in the Appendix below).
|
||||||
means this document.
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
1.9. "Licensable"
|
form, that is based on (or derived from) the Work and for which the
|
||||||
means having the right to grant, to the maximum extent possible,
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
whether at the time of the initial grant or subsequently, any and
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
all of the rights conveyed by this License.
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
1.10. "Modifications"
|
the Work and Derivative Works thereof.
|
||||||
means any of the following:
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
(a) any file in Source Code Form that results from an addition to,
|
the original version of the Work and any modifications or additions
|
||||||
deletion from, or modification of the contents of Covered
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
Software; or
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
(b) any new file in Source Code Form that contains any Covered
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
Software.
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
1.11. "Patent Claims" of a Contributor
|
communication on electronic mailing lists, source code control systems,
|
||||||
means any patent claim(s), including without limitation, method,
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
Contributor that would be infringed, but for the grant of the
|
excluding communication that is conspicuously marked or otherwise
|
||||||
License, by the making, using, selling, offering for sale, having
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
made, import, or transfer of either its Contributions or its
|
|
||||||
Contributor Version.
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
1.12. "Secondary License"
|
subsequently incorporated within the Work.
|
||||||
means either the GNU General Public License, Version 2.0, the GNU
|
|
||||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
Public License, Version 3.0, or any later versions of those
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
licenses.
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
1.13. "Source Code Form"
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
means the form of the work preferred for making modifications.
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
means an individual or a legal entity exercising rights under this
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
License. For legal entities, "You" includes any entity that
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
controls, is controlled by, or is under common control with You. For
|
(except as stated in this section) patent license to make, have made,
|
||||||
purposes of this definition, "control" means (a) the power, direct
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
or indirect, to cause the direction or management of such entity,
|
where such license applies only to those patent claims licensable
|
||||||
whether by contract or otherwise, or (b) ownership of more than
|
by such Contributor that are necessarily infringed by their
|
||||||
fifty percent (50%) of the outstanding shares or beneficial
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
ownership of such entity.
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
2. License Grants and Conditions
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
--------------------------------
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
2.1. Grants
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
(a) under intellectual property rights (other than patent or trademark)
|
modifications, and in Source or Object form, provided that You
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
meet the following conditions:
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
(a) You must give any other recipients of the Work or
|
||||||
as part of a Larger Work; and
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
(b) You must cause any modified files to carry prominent notices
|
||||||
for sale, have made, import, and otherwise transfer either its
|
stating that You changed the files; and
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
2.2. Effective Date
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
excluding those notices that do not pertain to any part of
|
||||||
become effective for each Contribution on the date the Contributor first
|
the Derivative Works; and
|
||||||
distributes such Contribution.
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
2.3. Limitations on Grant Scope
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
within such NOTICE file, excluding those notices that do not
|
||||||
this License. No additional rights or licenses will be implied from the
|
pertain to any part of the Derivative Works, in at least one
|
||||||
distribution or licensing of Covered Software under this License.
|
of the following places: within a NOTICE text file distributed
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
as part of the Derivative Works; within the Source form or
|
||||||
Contributor:
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
(a) for any code that a Contributor has removed from Covered Software;
|
wherever such third-party notices normally appear. The contents
|
||||||
or
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
(b) for infringements caused by: (i) Your and any other third party's
|
notices within Derivative Works that You distribute, alongside
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
Contributions with other software (except as part of its Contributor
|
that such additional attribution notices cannot be construed
|
||||||
Version); or
|
as modifying the License.
|
||||||
|
|
||||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
You may add Your own copyright statement to Your modifications and
|
||||||
its Contributions.
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
the notice requirements in Section 3.4).
|
the conditions stated in this License.
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
distribute the Covered Software under a subsequent version of this
|
this License, without any additional terms or conditions.
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
permitted under the terms of Section 3.3).
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
2.5. Representation
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
Each Contributor represents that the Contributor believes its
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
Contributions are its original creation(s) or it has sufficient rights
|
except as required for reasonable and customary use in describing the
|
||||||
to grant the rights to its Contributions conveyed by this License.
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
2.6. Fair Use
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
This License is not intended to limit any rights You have under
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
equivalents.
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
2.7. Conditions
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
risks associated with Your exercise of permissions under this License.
|
||||||
in Section 2.1.
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
3. Responsibilities
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
-------------------
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
3.1. Distribution of Source Form
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
result of this License or out of the use or inability to use the
|
||||||
Modifications that You create or to which You contribute, must be under
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
the terms of this License. You must inform recipients that the Source
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
other commercial damages or losses), even if such Contributor
|
||||||
License, and how they can obtain a copy of this License. You may not
|
has been advised of the possibility of such damages.
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
3.2. Distribution of Executable Form
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
If You distribute Covered Software in Executable Form then:
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
(a) such Covered Software must also be made available in Source Code
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
Form, as described in Section 3.1, and You must inform recipients of
|
defend, and hold each Contributor harmless for any liability
|
||||||
the Executable Form how they can obtain a copy of such Source Code
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
Form by reasonable means in a timely manner, at a charge no more
|
of your accepting any such warranty or additional liability.
|
||||||
than the cost of distribution to the recipient; and
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
(b) You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
license for the Executable Form does not attempt to limit or alter
|
|
||||||
the recipients' rights in the Source Code Form under this License.
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
3.3. Distribution of a Larger Work
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
comment syntax for the file format. We also recommend that a
|
||||||
provided that You also comply with the requirements of this License for
|
file or class name and description of purpose be included on the
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
same "printed page" as the copyright notice for easier
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
identification within third-party archives.
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
Copyright 2025-2026 DekinDev (Straice)
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
Software under the terms of either this License or such Secondary
|
you may not use this file except in compliance with the License.
|
||||||
License(s).
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
3.4. Notices
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
Unless required by applicable law or agreed to in writing, software
|
||||||
(including copyright notices, patent notices, disclaimers of warranty,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
or limitations of liability) contained within the Source Code Form of
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
the Covered Software, except that You may alter any license notices to
|
See the License for the specific language governing permissions and
|
||||||
the extent required to remedy known factual inaccuracies.
|
limitations under the License.
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this
|
|
||||||
License with respect to some or all of the Covered Software due to
|
|
||||||
statute, judicial order, or regulation then You must: (a) comply with
|
|
||||||
the terms of this License to the maximum extent possible; and (b)
|
|
||||||
describe the limitations and the code they affect. Such description must
|
|
||||||
be placed in a text file included with all distributions of the Covered
|
|
||||||
Software under this License. Except to the extent prohibited by statute
|
|
||||||
or regulation, such description must be sufficiently detailed for a
|
|
||||||
recipient of ordinary skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
--------------
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically
|
|
||||||
if You fail to comply with any of its terms. However, if You become
|
|
||||||
compliant, then the rights granted under this License from a particular
|
|
||||||
Contributor are reinstated (a) provisionally, unless and until such
|
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
||||||
ongoing basis, if such Contributor fails to notify You of the
|
|
||||||
non-compliance by some reasonable means prior to 60 days after You have
|
|
||||||
come back into compliance. Moreover, Your grants from a particular
|
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor
|
|
||||||
notifies You of the non-compliance by some reasonable means, this is the
|
|
||||||
first time You have received notice of non-compliance with this License
|
|
||||||
from such Contributor, and You become compliant prior to 30 days after
|
|
||||||
Your receipt of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
|
||||||
end user license agreements (excluding distributors and resellers) which
|
|
||||||
have been validly granted by You or Your distributors under this License
|
|
||||||
prior to termination shall survive termination.
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 6. Disclaimer of Warranty *
|
|
||||||
* ------------------------- *
|
|
||||||
* *
|
|
||||||
* Covered Software is provided under this License on an "as is" *
|
|
||||||
* basis, without warranty of any kind, either expressed, implied, or *
|
|
||||||
* statutory, including, without limitation, warranties that the *
|
|
||||||
* Covered Software is free of defects, merchantable, fit for a *
|
|
||||||
* particular purpose or non-infringing. The entire risk as to the *
|
|
||||||
* quality and performance of the Covered Software is with You. *
|
|
||||||
* Should any Covered Software prove defective in any respect, You *
|
|
||||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
|
||||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
|
||||||
* essential part of this License. No use of any Covered Software is *
|
|
||||||
* authorized under this License except under this disclaimer. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 7. Limitation of Liability *
|
|
||||||
* -------------------------- *
|
|
||||||
* *
|
|
||||||
* Under no circumstances and under no legal theory, whether tort *
|
|
||||||
* (including negligence), contract, or otherwise, shall any *
|
|
||||||
* Contributor, or anyone who distributes Covered Software as *
|
|
||||||
* permitted above, be liable to You for any direct, indirect, *
|
|
||||||
* special, incidental, or consequential damages of any character *
|
|
||||||
* including, without limitation, damages for lost profits, loss of *
|
|
||||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
|
||||||
* and all other commercial damages or losses, even if such party *
|
|
||||||
* shall have been informed of the possibility of such damages. This *
|
|
||||||
* limitation of liability shall not apply to liability for death or *
|
|
||||||
* personal injury resulting from such party's negligence to the *
|
|
||||||
* extent applicable law prohibits such limitation. Some *
|
|
||||||
* jurisdictions do not allow the exclusion or limitation of *
|
|
||||||
* incidental or consequential damages, so this exclusion and *
|
|
||||||
* limitation may not apply to You. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the
|
|
||||||
courts of a jurisdiction where the defendant maintains its principal
|
|
||||||
place of business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions.
|
|
||||||
Nothing in this Section shall prevent a party's ability to bring
|
|
||||||
cross-claims or counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides
|
|
||||||
that the language of a contract shall be construed against the drafter
|
|
||||||
shall not be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses
|
|
||||||
|
|
||||||
If You choose to distribute Source Code Form that is Incompatible With
|
|
||||||
Secondary Licenses under the terms of this version of the License, the
|
|
||||||
notice described in Exhibit B of this License must be attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
|
||||||
file in a relevant directory) where a recipient would be likely to look
|
|
||||||
for such a notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
defined by the Mozilla Public License, v. 2.0.
|
|
||||||
|
|||||||
4
NOTICE
Normal file
4
NOTICE
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Smooth Double Doors
|
||||||
|
Copyright 2025-2026 DekinDev (Straice)
|
||||||
|
|
||||||
|
This product includes software developed by DekinDev, https://straice.com.
|
||||||
12
README.md
12
README.md
@@ -1,2 +1,12 @@
|
|||||||
# Smooth-Double-Doors
|

|
||||||
|
|
||||||
|
Smooth Double Doors makes **paired doors open together** and adds **fluid animations** to **doors**, **trapdoors**, and **fence gates**. It keeps vanilla behavior and works with redstone, while letting you toggle features and adjust animation speed in‑game config. The mod is **texture‑pack friendly** and does **not replace models or require resource packs**, so it plays nicely with your existing packs.
|
||||||
|
|
||||||
|
Supports all door, trapdoor, and fence gate types.
|
||||||
|
|
||||||
|
By default, double trapdoors and double fence gates are disabled, but you can enable them in the mod’s settings. Everything is fully configurable!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
Requires [Mod Menu](https://modrinth.com/mod/modmenu) for the config screen.
|
||||||
88
build.gradle
Normal file
88
build.gradle
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
plugins {
|
||||||
|
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
|
||||||
|
id 'maven-publish'
|
||||||
|
}
|
||||||
|
|
||||||
|
version = project.mod_version
|
||||||
|
group = project.maven_group
|
||||||
|
|
||||||
|
base {
|
||||||
|
archivesName = project.archives_base_name
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url "https://maven.terraformersmc.com/releases/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
loom {
|
||||||
|
splitEnvironmentSourceSets()
|
||||||
|
|
||||||
|
mods {
|
||||||
|
"smooth-double-doors" {
|
||||||
|
sourceSet sourceSets.main
|
||||||
|
sourceSet sourceSets.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// To change the versions see the gradle.properties file
|
||||||
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
|
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
|
|
||||||
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
|
||||||
|
|
||||||
|
modCompileOnly "com.terraformersmc:modmenu:15.0.0"
|
||||||
|
modRuntimeOnly "com.terraformersmc:modmenu:15.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property "version", project.version
|
||||||
|
|
||||||
|
filesMatching("fabric.mod.json") {
|
||||||
|
expand "version": inputs.properties.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
it.options.release = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||||
|
// if it is present.
|
||||||
|
// If you remove this line, sources will not be generated.
|
||||||
|
withSourcesJar()
|
||||||
|
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
inputs.property "archivesName", project.base.archivesName
|
||||||
|
|
||||||
|
from("LICENSE") {
|
||||||
|
rename { "${it}_${inputs.properties.archivesName}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure the maven publication
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create("mavenJava", MavenPublication) {
|
||||||
|
artifactId = project.archives_base_name
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
||||||
|
repositories {
|
||||||
|
// Add repositories to publish to here.
|
||||||
|
// Notice: This block does NOT have the same function as the block in the top level.
|
||||||
|
// The repositories here will be used for publishing your artifact, not for
|
||||||
|
// retrieving dependencies.
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
espacio.png
Normal file
BIN
espacio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
21
gradle.properties
Normal file
21
gradle.properties
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Done to increase the memory available to gradle.
|
||||||
|
org.gradle.jvmargs=-Xmx1G
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
|
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
|
|
||||||
|
# Fabric Properties
|
||||||
|
# check these on https://fabricmc.net/develop
|
||||||
|
minecraft_version=1.21.8
|
||||||
|
yarn_mappings=1.21.8+build.1
|
||||||
|
loader_version=0.18.4
|
||||||
|
loom_version=1.14-SNAPSHOT
|
||||||
|
|
||||||
|
# Mod Properties
|
||||||
|
mod_version=1.0.0
|
||||||
|
maven_group=com.straice.smoothdoors
|
||||||
|
archives_base_name=smooth-double-doors
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
fabric_api_version=0.136.1+1.21.8
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
248
gradlew
vendored
Normal file
248
gradlew
vendored
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
93
gradlew.bat
vendored
Normal file
93
gradlew.bat
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
10
settings.gradle
Normal file
10
settings.gradle
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = 'Fabric'
|
||||||
|
url = 'https://maven.fabricmc.net/'
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.straice.smoothdoors;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
|
||||||
|
public class SmoothDoubleDoorsClient implements ClientModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
SddAnimator.initClientHooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.DoorBlock;
|
||||||
|
import net.minecraft.block.ShapeContext;
|
||||||
|
import net.minecraft.block.enums.DoorHinge;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Box;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.RotationAxis;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
|
||||||
|
final class DoorAnimation {
|
||||||
|
|
||||||
|
private DoorAnimation() {}
|
||||||
|
|
||||||
|
static void apply(BlockState state, float angleDeg, MatrixStack matrices, BlockRenderView world) {
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.get(DoorBlock.FACING);
|
||||||
|
DoorHinge hinge = state.get(DoorBlock.HINGE);
|
||||||
|
|
||||||
|
Direction hingeSide = (hinge == DoorHinge.RIGHT)
|
||||||
|
? facing.rotateYClockwise()
|
||||||
|
: facing.rotateYCounterclockwise();
|
||||||
|
|
||||||
|
Box bb = state.getOutlineShape(world, BlockPos.ORIGIN, ShapeContext.absent()).getBoundingBox();
|
||||||
|
|
||||||
|
float cx = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
float cz = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
|
||||||
|
float pivotX = cx;
|
||||||
|
float pivotZ = cz;
|
||||||
|
|
||||||
|
switch (hingeSide) {
|
||||||
|
case EAST -> pivotX = (float) bb.maxX;
|
||||||
|
case WEST -> pivotX = (float) bb.minX;
|
||||||
|
case SOUTH -> pivotZ = (float) bb.maxZ;
|
||||||
|
case NORTH -> pivotZ = (float) bb.minZ;
|
||||||
|
default -> {
|
||||||
|
pivotX = cx;
|
||||||
|
pivotZ = cz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float thickness = (facing == Direction.NORTH || facing == Direction.SOUTH)
|
||||||
|
? (float) (bb.maxZ - bb.minZ)
|
||||||
|
: (float) (bb.maxX - bb.minX);
|
||||||
|
float halfT = thickness * 0.5f;
|
||||||
|
|
||||||
|
float dX = 0.0f;
|
||||||
|
float dZ = 0.0f;
|
||||||
|
switch (facing) {
|
||||||
|
case NORTH -> dZ = halfT;
|
||||||
|
case SOUTH -> dZ = -halfT;
|
||||||
|
case WEST -> dX = halfT;
|
||||||
|
case EAST -> dX = -halfT;
|
||||||
|
default -> {
|
||||||
|
dX = 0.0f;
|
||||||
|
dZ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double rad = Math.toRadians(angleDeg);
|
||||||
|
float cos = (float) Math.cos(rad);
|
||||||
|
float sin = (float) Math.sin(rad);
|
||||||
|
float rotDX = dX * cos - dZ * sin;
|
||||||
|
float rotDZ = dX * sin + dZ * cos;
|
||||||
|
float shiftX = dX - rotDX;
|
||||||
|
float shiftZ = dZ - rotDZ;
|
||||||
|
|
||||||
|
matrices.translate(shiftX, 0.0f, shiftZ);
|
||||||
|
matrices.translate(pivotX, 0.0f, pivotZ);
|
||||||
|
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angleDeg));
|
||||||
|
matrices.translate(-pivotX, 0.0f, -pivotZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.FenceGateBlock;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.RenderLayers;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
|
import net.minecraft.client.render.block.BlockModelRenderer;
|
||||||
|
import net.minecraft.client.render.model.BakedQuad;
|
||||||
|
import net.minecraft.client.render.model.BlockModelPart;
|
||||||
|
import net.minecraft.client.render.model.BlockStateModel;
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.RotationAxis;
|
||||||
|
import net.minecraft.util.math.random.Random;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class FenceGateAnimation {
|
||||||
|
|
||||||
|
private static final float POST_EDGE = 2.0f / 16.0f;
|
||||||
|
private static final float EDGE_EPS = 1.0e-4f;
|
||||||
|
|
||||||
|
private FenceGateAnimation() {}
|
||||||
|
|
||||||
|
static void render(BlockState state, float angleDeg, MatrixStack matrices,
|
||||||
|
VertexConsumerProvider consumers, int light, int overlay) {
|
||||||
|
MinecraftClient mc = MinecraftClient.getInstance();
|
||||||
|
if (mc.world == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.get(FenceGateBlock.FACING);
|
||||||
|
boolean leftRightIsX = facing.getAxis() == Direction.Axis.Z;
|
||||||
|
|
||||||
|
BlockStateModel baseModel = mc.getBlockRenderManager().getModel(state);
|
||||||
|
FenceGateModels models = splitFenceGateModels(baseModel, leftRightIsX);
|
||||||
|
if (models.isEmpty()) return;
|
||||||
|
|
||||||
|
int tint = mc.getBlockColors().getColor(state, null, null, 0);
|
||||||
|
float r = ((tint >> 16) & 0xFF) / 255.0f;
|
||||||
|
float g = ((tint >> 8) & 0xFF) / 255.0f;
|
||||||
|
float b = (tint & 0xFF) / 255.0f;
|
||||||
|
|
||||||
|
VertexConsumer consumer = consumers.getBuffer(RenderLayers.getEntityBlockLayer(state));
|
||||||
|
|
||||||
|
if (models.posts != null) {
|
||||||
|
BlockModelRenderer.render(matrices.peek(), consumer, models.posts, r, g, b, light, overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
float leftPivotX = leftRightIsX ? (1.0f / 16.0f) : 0.5f;
|
||||||
|
float leftPivotZ = leftRightIsX ? 0.5f : (1.0f / 16.0f);
|
||||||
|
float rightPivotX = leftRightIsX ? (15.0f / 16.0f) : 0.5f;
|
||||||
|
float rightPivotZ = leftRightIsX ? 0.5f : (15.0f / 16.0f);
|
||||||
|
|
||||||
|
float sign = (facing == Direction.SOUTH || facing == Direction.EAST) ? 1.0f : -1.0f;
|
||||||
|
float signedAngle = angleDeg * sign;
|
||||||
|
float leftAngle = leftRightIsX ? -signedAngle : signedAngle;
|
||||||
|
float rightAngle = leftRightIsX ? signedAngle : -signedAngle;
|
||||||
|
|
||||||
|
if (models.left != null) {
|
||||||
|
matrices.push();
|
||||||
|
matrices.translate(leftPivotX, 0.0f, leftPivotZ);
|
||||||
|
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(leftAngle));
|
||||||
|
matrices.translate(-leftPivotX, 0.0f, -leftPivotZ);
|
||||||
|
BlockModelRenderer.render(matrices.peek(), consumer, models.left, r, g, b, light, overlay);
|
||||||
|
matrices.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.right != null) {
|
||||||
|
matrices.push();
|
||||||
|
matrices.translate(rightPivotX, 0.0f, rightPivotZ);
|
||||||
|
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rightAngle));
|
||||||
|
matrices.translate(-rightPivotX, 0.0f, -rightPivotZ);
|
||||||
|
BlockModelRenderer.render(matrices.peek(), consumer, models.right, r, g, b, light, overlay);
|
||||||
|
matrices.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FenceGateModels splitFenceGateModels(BlockStateModel model, boolean leftRightIsX) {
|
||||||
|
List<BlockModelPart> parts = model.getParts(Random.create(42L));
|
||||||
|
List<BlockModelPart> posts = new ArrayList<>();
|
||||||
|
List<BlockModelPart> left = new ArrayList<>();
|
||||||
|
List<BlockModelPart> right = new ArrayList<>();
|
||||||
|
|
||||||
|
for (BlockModelPart part : parts) {
|
||||||
|
FenceGateCollector postsCollector = new FenceGateCollector();
|
||||||
|
FenceGateCollector leftCollector = new FenceGateCollector();
|
||||||
|
FenceGateCollector rightCollector = new FenceGateCollector();
|
||||||
|
|
||||||
|
for (Direction dir : Direction.values()) {
|
||||||
|
addFenceGateQuads(part, dir, leftRightIsX, postsCollector, leftCollector, rightCollector);
|
||||||
|
}
|
||||||
|
addFenceGateQuads(part, null, leftRightIsX, postsCollector, leftCollector, rightCollector);
|
||||||
|
|
||||||
|
if (!postsCollector.isEmpty()) posts.add(postsCollector.toPart(part));
|
||||||
|
if (!leftCollector.isEmpty()) left.add(leftCollector.toPart(part));
|
||||||
|
if (!rightCollector.isEmpty()) right.add(rightCollector.toPart(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FenceGateModels(
|
||||||
|
posts.isEmpty() ? null : new StaticBlockStateModel(posts, model.particleSprite()),
|
||||||
|
left.isEmpty() ? null : new StaticBlockStateModel(left, model.particleSprite()),
|
||||||
|
right.isEmpty() ? null : new StaticBlockStateModel(right, model.particleSprite())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addFenceGateQuads(BlockModelPart part, Direction dir, boolean leftRightIsX,
|
||||||
|
FenceGateCollector posts, FenceGateCollector left, FenceGateCollector right) {
|
||||||
|
List<BakedQuad> quads = part.getQuads(dir);
|
||||||
|
if (quads.isEmpty()) return;
|
||||||
|
|
||||||
|
for (BakedQuad quad : quads) {
|
||||||
|
FenceGateSection section = classifyFenceGateQuad(quad, leftRightIsX);
|
||||||
|
switch (section) {
|
||||||
|
case POSTS -> posts.add(dir, quad);
|
||||||
|
case LEFT -> addDoubleSided(left, dir, quad);
|
||||||
|
case RIGHT -> addDoubleSided(right, dir, quad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FenceGateSection classifyFenceGateQuad(BakedQuad quad, boolean leftRightIsX) {
|
||||||
|
int[] data = quad.vertexData();
|
||||||
|
if (data.length < 8) return FenceGateSection.LEFT;
|
||||||
|
|
||||||
|
int stride = data.length / 4;
|
||||||
|
float min = Float.POSITIVE_INFINITY;
|
||||||
|
float max = Float.NEGATIVE_INFINITY;
|
||||||
|
int coordOffset = leftRightIsX ? 0 : 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int base = i * stride + coordOffset;
|
||||||
|
float coord = Float.intBitsToFloat(data[base]);
|
||||||
|
min = Math.min(min, coord);
|
||||||
|
max = Math.max(max, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max <= POST_EDGE + EDGE_EPS || min >= (1.0f - POST_EDGE) - EDGE_EPS) {
|
||||||
|
return FenceGateSection.POSTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
float center = (min + max) * 0.5f;
|
||||||
|
return center <= 0.5f ? FenceGateSection.LEFT : FenceGateSection.RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FenceGateSection { POSTS, LEFT, RIGHT }
|
||||||
|
|
||||||
|
private static void addDoubleSided(FenceGateCollector target, Direction dir, BakedQuad quad) {
|
||||||
|
target.add(dir, quad);
|
||||||
|
target.add(dir, reverseQuad(quad));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BakedQuad reverseQuad(BakedQuad quad) {
|
||||||
|
int[] data = quad.vertexData();
|
||||||
|
int stride = data.length / 4;
|
||||||
|
int[] flipped = new int[data.length];
|
||||||
|
|
||||||
|
for (int v = 0; v < 4; v++) {
|
||||||
|
int src = (3 - v) * stride;
|
||||||
|
int dst = v * stride;
|
||||||
|
System.arraycopy(data, src, flipped, dst, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
Direction face = quad.face();
|
||||||
|
Direction opposite = (face == null) ? null : face.getOpposite();
|
||||||
|
return new BakedQuad(flipped, quad.tintIndex(), opposite, quad.sprite(), quad.shade(), quad.lightEmission());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FenceGateModels {
|
||||||
|
final BlockStateModel posts;
|
||||||
|
final BlockStateModel left;
|
||||||
|
final BlockStateModel right;
|
||||||
|
|
||||||
|
FenceGateModels(BlockStateModel posts, BlockStateModel left, BlockStateModel right) {
|
||||||
|
this.posts = posts;
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return posts == null && left == null && right == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FenceGateCollector {
|
||||||
|
private final EnumMap<Direction, List<BakedQuad>> faceQuads = new EnumMap<>(Direction.class);
|
||||||
|
private final List<BakedQuad> unculled = new ArrayList<>();
|
||||||
|
|
||||||
|
void add(Direction face, BakedQuad quad) {
|
||||||
|
if (face == null) {
|
||||||
|
unculled.add(quad);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
faceQuads.computeIfAbsent(face, k -> new ArrayList<>()).add(quad);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return unculled.isEmpty() && faceQuads.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockModelPart toPart(BlockModelPart template) {
|
||||||
|
return new StaticBlockModelPart(faceQuads, unculled, template.useAmbientOcclusion(), template.particleSprite());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StaticBlockModelPart implements BlockModelPart {
|
||||||
|
private final EnumMap<Direction, List<BakedQuad>> faceQuads;
|
||||||
|
private final List<BakedQuad> unculled;
|
||||||
|
private final boolean useAo;
|
||||||
|
private final Sprite particleSprite;
|
||||||
|
|
||||||
|
StaticBlockModelPart(EnumMap<Direction, List<BakedQuad>> faceQuads, List<BakedQuad> unculled,
|
||||||
|
boolean useAo, Sprite particleSprite) {
|
||||||
|
this.faceQuads = faceQuads;
|
||||||
|
this.unculled = unculled;
|
||||||
|
this.useAo = useAo;
|
||||||
|
this.particleSprite = particleSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BakedQuad> getQuads(Direction face) {
|
||||||
|
if (face == null) return unculled;
|
||||||
|
List<BakedQuad> quads = faceQuads.get(face);
|
||||||
|
return (quads == null) ? List.of() : quads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useAmbientOcclusion() {
|
||||||
|
return useAo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sprite particleSprite() {
|
||||||
|
return particleSprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StaticBlockStateModel implements BlockStateModel {
|
||||||
|
private final List<BlockModelPart> parts;
|
||||||
|
private final Sprite particleSprite;
|
||||||
|
|
||||||
|
StaticBlockStateModel(List<BlockModelPart> parts, Sprite particleSprite) {
|
||||||
|
this.parts = parts;
|
||||||
|
this.particleSprite = particleSprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addParts(Random random, List<BlockModelPart> out) {
|
||||||
|
out.addAll(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sprite particleSprite() {
|
||||||
|
return particleSprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfig;
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||||
|
import net.minecraft.block.*;
|
||||||
|
import net.minecraft.block.enums.DoorHinge;
|
||||||
|
import net.minecraft.block.enums.DoubleBlockHalf;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.OverlayTexture;
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
|
import net.minecraft.client.render.WorldRenderer;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
public final class SddAnimator {
|
||||||
|
|
||||||
|
private static final MinecraftClient MC = MinecraftClient.getInstance();
|
||||||
|
|
||||||
|
// Base duration (seconds) at speed = 1.0x
|
||||||
|
private static final float BASE_DURATION_S = 0.35f;
|
||||||
|
|
||||||
|
private static final Long2ObjectOpenHashMap<Anim> ANIMS = new Long2ObjectOpenHashMap<>();
|
||||||
|
private static boolean hooksInit = false;
|
||||||
|
|
||||||
|
private SddAnimator() {}
|
||||||
|
|
||||||
|
/** Llamar desde SmoothDoubleDoorsClient#onInitializeClient(). */
|
||||||
|
public static void initClientHooks() {
|
||||||
|
if (hooksInit) return;
|
||||||
|
hooksInit = true;
|
||||||
|
|
||||||
|
WorldRenderEvents.AFTER_ENTITIES.register(ctx -> {
|
||||||
|
if (MC.world == null || ANIMS.isEmpty()) return;
|
||||||
|
float tickDelta = MC.getRenderTickCounter().getTickProgress(true);
|
||||||
|
renderAll(tickDelta);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// === API usada por mixins ===
|
||||||
|
|
||||||
|
/** Oculta el bloque vanilla mientras animamos (evita doble puerta). */
|
||||||
|
public static boolean shouldHideInChunk(BlockPos pos, BlockState state) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
if (ANIMS.containsKey(pos.asLong())) return true;
|
||||||
|
|
||||||
|
if (state.getBlock() instanceof DoorBlock && state.contains(DoorBlock.HALF)) {
|
||||||
|
DoubleBlockHalf half = state.get(DoorBlock.HALF);
|
||||||
|
BlockPos other = (half == DoubleBlockHalf.LOWER) ? pos.up() : pos.down();
|
||||||
|
return ANIMS.containsKey(other.asLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAnimatingAt(BlockPos pos) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
return ANIMS.containsKey(pos.asLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Se llama cuando cambia el bloque (abrir/cerrar). */
|
||||||
|
public static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) {
|
||||||
|
if (MC.world == null) return;
|
||||||
|
|
||||||
|
Kind kind = kindOf(oldState, newState);
|
||||||
|
if (kind == null) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.remove(pos.asLong());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKindBlock(kind, newState)) {
|
||||||
|
removeKindAt(pos, kind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOOR: arrancamos solo desde la mitad LOWER para no duplicar.
|
||||||
|
if (kind == Kind.DOOR && newState.contains(DoorBlock.HALF)) {
|
||||||
|
if (newState.get(DoorBlock.HALF) == DoubleBlockHalf.UPPER) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean oldOpen = isOpen(oldState);
|
||||||
|
boolean newOpen = isOpen(newState);
|
||||||
|
if (oldOpen == newOpen) return;
|
||||||
|
|
||||||
|
SddConfig cfg = SddConfigManager.get();
|
||||||
|
if (!isAnimationEnabled(cfg, kind)) {
|
||||||
|
removeKindAt(pos, kind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float speed = speedFor(cfg, kind);
|
||||||
|
if (speed <= 0.001f) speed = 1.0f;
|
||||||
|
|
||||||
|
long startTick = MC.world.getTime();
|
||||||
|
|
||||||
|
if (kind == Kind.DOOR) {
|
||||||
|
startDoorAnimBothHalves(pos, newState, oldOpen, newOpen, speed, startTick);
|
||||||
|
} else {
|
||||||
|
BlockState base = forceClosedModel(newState, kind);
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.put(pos.asLong(), new Anim(pos.toImmutable(), base, kind, oldOpen, newOpen, speed, startTick));
|
||||||
|
}
|
||||||
|
requestRerender(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Render ===
|
||||||
|
|
||||||
|
private static void renderAll(float tickDelta) {
|
||||||
|
if (MC.world == null || ANIMS.isEmpty()) return;
|
||||||
|
|
||||||
|
Vec3d camPos = MC.gameRenderer.getCamera().getPos();
|
||||||
|
MatrixStack matrices = new MatrixStack();
|
||||||
|
VertexConsumerProvider.Immediate consumers = MC.getBufferBuilders().getEntityVertexConsumers();
|
||||||
|
|
||||||
|
long worldTick = MC.world.getTime();
|
||||||
|
double nowTick = worldTick + (double) MathHelper.clamp(tickDelta, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
Anim[] snapshot;
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
snapshot = ANIMS.values().toArray(new Anim[0]);
|
||||||
|
}
|
||||||
|
for (Anim a : snapshot) {
|
||||||
|
float t = a.progress01(nowTick);
|
||||||
|
renderOne(a, t, camPos, matrices, consumers);
|
||||||
|
}
|
||||||
|
|
||||||
|
consumers.draw();
|
||||||
|
cleanupFinished(nowTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cleanupFinished(double nowTick) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
var it = ANIMS.long2ObjectEntrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var entry = it.next();
|
||||||
|
if (entry.getValue().isFinished(nowTick)) {
|
||||||
|
BlockPos pos = entry.getValue().pos;
|
||||||
|
it.remove();
|
||||||
|
requestRerender(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderOne(Anim anim, float t, Vec3d camPos, MatrixStack matrices, VertexConsumerProvider consumers) {
|
||||||
|
if (MC.world == null) return;
|
||||||
|
|
||||||
|
BlockPos pos = anim.pos;
|
||||||
|
BlockState state = anim.baseState;
|
||||||
|
|
||||||
|
matrices.push();
|
||||||
|
|
||||||
|
// Fijo en mundo: bloque - camara
|
||||||
|
matrices.translate(
|
||||||
|
pos.getX() - camPos.x,
|
||||||
|
pos.getY() - camPos.y,
|
||||||
|
pos.getZ() - camPos.z
|
||||||
|
);
|
||||||
|
|
||||||
|
float eased = easeInOut(t);
|
||||||
|
float angleDeg = lerpAngleDeg(anim.fromOpen, anim.toOpen, eased, anim.kind, state);
|
||||||
|
|
||||||
|
int light = WorldRenderer.getLightmapCoordinates((net.minecraft.world.BlockRenderView) MC.world, pos);
|
||||||
|
if (anim.kind == Kind.FENCE_GATE) {
|
||||||
|
FenceGateAnimation.render(state, angleDeg, matrices, consumers, light, OverlayTexture.DEFAULT_UV);
|
||||||
|
} else {
|
||||||
|
applyTransform(anim.kind, state, angleDeg, matrices);
|
||||||
|
MC.getBlockRenderManager().renderBlockAsEntity(state, matrices, consumers, light, OverlayTexture.DEFAULT_UV);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DOOR (dos mitades) ===
|
||||||
|
|
||||||
|
private static void startDoorAnimBothHalves(BlockPos lowerPos, BlockState lowerNewState, boolean oldOpen, boolean newOpen, float speed, long startTick) {
|
||||||
|
BlockState baseLower = forceClosedModel(lowerNewState, Kind.DOOR);
|
||||||
|
|
||||||
|
BlockPos upperPos = lowerPos.up();
|
||||||
|
BlockState baseUpper = baseLower.with(DoorBlock.HALF, DoubleBlockHalf.UPPER);
|
||||||
|
|
||||||
|
Anim aLower = new Anim(lowerPos.toImmutable(), baseLower, Kind.DOOR, oldOpen, newOpen, speed, startTick);
|
||||||
|
Anim aUpper = new Anim(upperPos.toImmutable(), baseUpper, Kind.DOOR, oldOpen, newOpen, speed, startTick);
|
||||||
|
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.put(lowerPos.asLong(), aLower);
|
||||||
|
ANIMS.put(upperPos.asLong(), aUpper);
|
||||||
|
}
|
||||||
|
requestRerender(lowerPos);
|
||||||
|
requestRerender(upperPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeKindAt(BlockPos pos, Kind kind) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.remove(pos.asLong());
|
||||||
|
if (kind == Kind.DOOR) {
|
||||||
|
ANIMS.remove(pos.up().asLong());
|
||||||
|
ANIMS.remove(pos.down().asLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Angulos y transforms ===
|
||||||
|
|
||||||
|
private static float lerpAngleDeg(boolean fromOpen, boolean toOpen, float t, Kind kind, BlockState state) {
|
||||||
|
float a = angleFor(kind, state, fromOpen);
|
||||||
|
float b = angleFor(kind, state, toOpen);
|
||||||
|
return MathHelper.lerp(t, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float angleFor(Kind kind, BlockState state, boolean open) {
|
||||||
|
if (!open) return 0.0f;
|
||||||
|
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> {
|
||||||
|
DoorHinge hinge = state.get(DoorBlock.HINGE);
|
||||||
|
yield (hinge == DoorHinge.RIGHT) ? -90.0f : 90.0f;
|
||||||
|
}
|
||||||
|
case TRAPDOOR -> 90.0f;
|
||||||
|
case FENCE_GATE -> 90.0f;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyTransform(Kind kind, BlockState state, float angleDeg, MatrixStack matrices) {
|
||||||
|
switch (kind) {
|
||||||
|
case DOOR -> DoorAnimation.apply(state, angleDeg, matrices, MC.world);
|
||||||
|
case TRAPDOOR -> TrapdoorAnimation.apply(state, angleDeg, matrices, MC.world);
|
||||||
|
case FENCE_GATE -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Utilidades ===
|
||||||
|
|
||||||
|
private static BlockState forceClosedModel(BlockState s, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> s.with(DoorBlock.OPEN, false);
|
||||||
|
case TRAPDOOR -> s.with(TrapdoorBlock.OPEN, false);
|
||||||
|
case FENCE_GATE -> s.with(FenceGateBlock.OPEN, false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isKindBlock(Kind kind, BlockState state) {
|
||||||
|
Block b = state.getBlock();
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> b instanceof DoorBlock;
|
||||||
|
case TRAPDOOR -> b instanceof TrapdoorBlock;
|
||||||
|
case FENCE_GATE -> b instanceof FenceGateBlock;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void requestRerender(BlockPos pos) {
|
||||||
|
if (MC.worldRenderer == null || MC.world == null) return;
|
||||||
|
BlockState st = MC.world.getBlockState(pos);
|
||||||
|
MC.worldRenderer.updateBlock(MC.world, pos, st, st, 0);
|
||||||
|
MC.worldRenderer.scheduleBlockRenders(
|
||||||
|
pos.getX(), pos.getY(), pos.getZ(),
|
||||||
|
pos.getX(), pos.getY(), pos.getZ()
|
||||||
|
);
|
||||||
|
MC.worldRenderer.scheduleChunkRender(
|
||||||
|
pos.getX() >> 4,
|
||||||
|
pos.getY() >> 4,
|
||||||
|
pos.getZ() >> 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isOpen(BlockState s) {
|
||||||
|
Block b = s.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return s.get(DoorBlock.OPEN);
|
||||||
|
if (b instanceof TrapdoorBlock) return s.get(TrapdoorBlock.OPEN);
|
||||||
|
if (b instanceof FenceGateBlock) return s.get(FenceGateBlock.OPEN);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Kind kindOf(BlockState oldState, BlockState newState) {
|
||||||
|
Block b = newState.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
||||||
|
if (b instanceof TrapdoorBlock) return Kind.TRAPDOOR;
|
||||||
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
||||||
|
|
||||||
|
b = oldState.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
||||||
|
if (b instanceof TrapdoorBlock) return Kind.TRAPDOOR;
|
||||||
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAnimationEnabled(SddConfig cfg, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> cfg.animateDoors;
|
||||||
|
case TRAPDOOR -> cfg.animateTrapdoors;
|
||||||
|
case FENCE_GATE -> getBool(cfg, "animateFenceGates", false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float speedFor(SddConfig cfg, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> cfg.doorSpeed;
|
||||||
|
case TRAPDOOR -> cfg.trapdoorSpeed;
|
||||||
|
case FENCE_GATE -> getFloat(cfg, "fenceGateSpeed", 1.0f);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float easeInOut(float t) {
|
||||||
|
return t * t * (3.0f - 2.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Reflect helpers (por si el campo no existe) ===
|
||||||
|
|
||||||
|
private static boolean getBool(Object obj, String field, boolean def) {
|
||||||
|
try {
|
||||||
|
Field f = obj.getClass().getDeclaredField(field);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object v = f.get(obj);
|
||||||
|
return (v instanceof Boolean) ? (Boolean) v : def;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getFloat(Object obj, String field, float def) {
|
||||||
|
try {
|
||||||
|
Field f = obj.getClass().getDeclaredField(field);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object v = f.get(obj);
|
||||||
|
if (v instanceof Float) return (Float) v;
|
||||||
|
if (v instanceof Double) return ((Double) v).floatValue();
|
||||||
|
if (v instanceof Number) return ((Number) v).floatValue();
|
||||||
|
return def;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Tipos ===
|
||||||
|
|
||||||
|
public enum Kind { DOOR, TRAPDOOR, FENCE_GATE }
|
||||||
|
|
||||||
|
private static final class Anim {
|
||||||
|
final BlockPos pos;
|
||||||
|
final BlockState baseState;
|
||||||
|
final Kind kind;
|
||||||
|
final boolean fromOpen;
|
||||||
|
final boolean toOpen;
|
||||||
|
|
||||||
|
final double startTick;
|
||||||
|
final double durationTicks;
|
||||||
|
|
||||||
|
Anim(BlockPos pos, BlockState baseState, Kind kind, boolean fromOpen, boolean toOpen, float speed, long startTick) {
|
||||||
|
this.pos = pos;
|
||||||
|
this.baseState = baseState;
|
||||||
|
this.kind = kind;
|
||||||
|
this.fromOpen = fromOpen;
|
||||||
|
this.toOpen = toOpen;
|
||||||
|
|
||||||
|
double durS = BASE_DURATION_S / speed;
|
||||||
|
if (durS < 0.05) durS = 0.05;
|
||||||
|
|
||||||
|
this.startTick = (double) startTick;
|
||||||
|
this.durationTicks = Math.max(1.0, durS * 20.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float progress01(double nowTick) {
|
||||||
|
double t = (nowTick - startTick) / durationTicks;
|
||||||
|
if (t <= 0.0) return 0.0f;
|
||||||
|
if (t >= 1.0) return 1.0f;
|
||||||
|
return (float) t;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isFinished(double nowTick) {
|
||||||
|
return (nowTick - startTick) >= durationTicks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.ShapeContext;
|
||||||
|
import net.minecraft.block.TrapdoorBlock;
|
||||||
|
import net.minecraft.block.enums.BlockHalf;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Box;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.RotationAxis;
|
||||||
|
import net.minecraft.util.shape.VoxelShape;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
|
||||||
|
final class TrapdoorAnimation {
|
||||||
|
|
||||||
|
private TrapdoorAnimation() {}
|
||||||
|
|
||||||
|
static void apply(BlockState state, float baseAngleDeg, MatrixStack matrices, BlockRenderView world) {
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.get(TrapdoorBlock.FACING);
|
||||||
|
BlockHalf half = state.get(TrapdoorBlock.HALF);
|
||||||
|
|
||||||
|
VoxelShape collShape = state.getCollisionShape(world, BlockPos.ORIGIN, ShapeContext.absent());
|
||||||
|
Box bb = collShape.isEmpty()
|
||||||
|
? state.getOutlineShape(world, BlockPos.ORIGIN, ShapeContext.absent()).getBoundingBox()
|
||||||
|
: collShape.getBoundingBox();
|
||||||
|
|
||||||
|
Direction hingeSide = facing.getOpposite();
|
||||||
|
|
||||||
|
float pivotX = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
float pivotZ = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
switch (hingeSide) {
|
||||||
|
case NORTH -> pivotZ = (float) bb.minZ;
|
||||||
|
case SOUTH -> pivotZ = (float) bb.maxZ;
|
||||||
|
case WEST -> pivotX = (float) bb.minX;
|
||||||
|
case EAST -> pivotX = (float) bb.maxX;
|
||||||
|
default -> {
|
||||||
|
pivotX = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
pivotZ = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float pivotY = (half == BlockHalf.TOP) ? (float) bb.maxY : (float) bb.minY;
|
||||||
|
|
||||||
|
float angle;
|
||||||
|
switch (hingeSide) {
|
||||||
|
case NORTH, EAST -> angle = -baseAngleDeg;
|
||||||
|
case SOUTH, WEST -> angle = baseAngleDeg;
|
||||||
|
default -> angle = baseAngleDeg;
|
||||||
|
}
|
||||||
|
if (half == BlockHalf.TOP) angle = -angle;
|
||||||
|
|
||||||
|
float thickness = (float) (bb.maxY - bb.minY);
|
||||||
|
float halfT = thickness > 0.0f ? thickness * 0.5f : (3.0f / 32.0f);
|
||||||
|
float dY = (half == BlockHalf.TOP) ? -halfT : halfT;
|
||||||
|
|
||||||
|
double rad = Math.toRadians(angle);
|
||||||
|
float cos = (float) Math.cos(rad);
|
||||||
|
float sin = (float) Math.sin(rad);
|
||||||
|
|
||||||
|
float shiftX = 0.0f;
|
||||||
|
float shiftY = 0.0f;
|
||||||
|
float shiftZ = 0.0f;
|
||||||
|
if (hingeSide == Direction.NORTH || hingeSide == Direction.SOUTH) {
|
||||||
|
float rotY = dY * cos;
|
||||||
|
float rotZ = dY * sin;
|
||||||
|
shiftY = dY - rotY;
|
||||||
|
shiftZ = -rotZ;
|
||||||
|
} else {
|
||||||
|
float rotX = -dY * sin;
|
||||||
|
float rotY = dY * cos;
|
||||||
|
shiftX = -rotX;
|
||||||
|
shiftY = dY - rotY;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices.translate(shiftX, shiftY, shiftZ);
|
||||||
|
matrices.translate(pivotX, pivotY, pivotZ);
|
||||||
|
if (hingeSide == Direction.NORTH || hingeSide == Direction.SOUTH) {
|
||||||
|
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle));
|
||||||
|
} else {
|
||||||
|
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle));
|
||||||
|
}
|
||||||
|
matrices.translate(-pivotX, -pivotY, -pivotZ);
|
||||||
|
matrices.translate(-shiftX, -shiftY, -shiftZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package com.straice.smoothdoors.client.ui;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfig;
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
|
import net.minecraft.client.gui.Element;
|
||||||
|
import net.minecraft.client.gui.Selectable;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||||
|
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||||
|
import net.minecraft.client.gui.widget.ElementListWidget;
|
||||||
|
import net.minecraft.client.gui.widget.SliderWidget;
|
||||||
|
import net.minecraft.client.gui.widget.ThreePartsLayoutWidget;
|
||||||
|
import net.minecraft.screen.ScreenTexts;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class SddConfigScreen extends Screen {
|
||||||
|
private static final int ITEM_HEIGHT = 24;
|
||||||
|
private static final int ROW_WIDTH = 310;
|
||||||
|
private static final int COLUMN_WIDTH = 150;
|
||||||
|
private static final int COLUMN_GAP = 10;
|
||||||
|
private static final int GROUP_GAP = 10;
|
||||||
|
|
||||||
|
private final Screen parent;
|
||||||
|
private final SddConfig cfg;
|
||||||
|
private final ThreePartsLayoutWidget layout = new ThreePartsLayoutWidget(this);
|
||||||
|
private SettingsList list;
|
||||||
|
|
||||||
|
public SddConfigScreen(Screen parent) {
|
||||||
|
super(Text.literal("Smooth Double Doors - Settings"));
|
||||||
|
this.parent = parent;
|
||||||
|
this.cfg = SddConfigManager.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
initHeader();
|
||||||
|
initBody();
|
||||||
|
initFooter();
|
||||||
|
|
||||||
|
layout.forEachChild(this::addDrawableChild);
|
||||||
|
refreshWidgetPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHeader() {
|
||||||
|
layout.addHeader(this.title, this.textRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initBody() {
|
||||||
|
if (this.client == null) return;
|
||||||
|
list = layout.addBody(new SettingsList(this.client, this.width, layout));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Double Doors: " + (cfg.connectDoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.connectDoors = !cfg.connectDoors),
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Redstone Activable: " + (cfg.redstoneDoubleDoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.redstoneDoubleDoors = !cfg.redstoneDoubleDoors)
|
||||||
|
));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Door Animation: " + (cfg.animateDoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.animateDoors = !cfg.animateDoors),
|
||||||
|
new SpeedSlider(COLUMN_WIDTH, "Animation Speed", cfg.doorSpeed, v -> {
|
||||||
|
cfg.doorSpeed = v;
|
||||||
|
SddConfigManager.save();
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Double Trapdoors: " + (cfg.connectTrapdoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.connectTrapdoors = !cfg.connectTrapdoors),
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Redstone Activable: " + (cfg.redstoneDoubleTrapdoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.redstoneDoubleTrapdoors = !cfg.redstoneDoubleTrapdoors),
|
||||||
|
GROUP_GAP
|
||||||
|
));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Trapdoor Animation: " + (cfg.animateTrapdoors ? "ON" : "OFF"),
|
||||||
|
() -> cfg.animateTrapdoors = !cfg.animateTrapdoors),
|
||||||
|
new SpeedSlider(COLUMN_WIDTH, "Animation Speed", cfg.trapdoorSpeed, v -> {
|
||||||
|
cfg.trapdoorSpeed = v;
|
||||||
|
SddConfigManager.save();
|
||||||
|
}),
|
||||||
|
GROUP_GAP
|
||||||
|
));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Double Fence Gates: " + (cfg.connectFenceGates ? "ON" : "OFF"),
|
||||||
|
() -> cfg.connectFenceGates = !cfg.connectFenceGates),
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Redstone Activable: " + (cfg.redstoneDoubleFenceGates ? "ON" : "OFF"),
|
||||||
|
() -> cfg.redstoneDoubleFenceGates = !cfg.redstoneDoubleFenceGates),
|
||||||
|
GROUP_GAP * 2
|
||||||
|
));
|
||||||
|
|
||||||
|
list.addEntry(new SettingsEntry(
|
||||||
|
toggle(COLUMN_WIDTH, () -> "Fence Gate Animation: " + (cfg.animateFenceGates ? "ON" : "OFF"),
|
||||||
|
() -> cfg.animateFenceGates = !cfg.animateFenceGates),
|
||||||
|
new SpeedSlider(COLUMN_WIDTH, "Animation Speed", cfg.fenceGateSpeed, v -> {
|
||||||
|
cfg.fenceGateSpeed = v;
|
||||||
|
SddConfigManager.save();
|
||||||
|
}),
|
||||||
|
GROUP_GAP * 2
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFooter() {
|
||||||
|
layout.addFooter(ButtonWidget.builder(ScreenTexts.DONE, btn -> {
|
||||||
|
SddConfigManager.save();
|
||||||
|
if (this.client != null) this.client.setScreen(parent);
|
||||||
|
}).width(200).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void refreshWidgetPositions() {
|
||||||
|
layout.refreshPositions();
|
||||||
|
if (list != null) {
|
||||||
|
list.position(this.width, layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
SddConfigManager.save();
|
||||||
|
if (this.client != null) this.client.setScreen(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ButtonWidget toggle(int width, java.util.function.Supplier<String> label, Runnable action) {
|
||||||
|
return ButtonWidget.builder(Text.literal(label.get()), btn -> {
|
||||||
|
action.run();
|
||||||
|
btn.setMessage(Text.literal(label.get()));
|
||||||
|
SddConfigManager.save();
|
||||||
|
}).dimensions(0, 0, width, 20).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SettingsList extends ElementListWidget<SettingsEntry> {
|
||||||
|
SettingsList(MinecraftClient client, int width, ThreePartsLayoutWidget layout) {
|
||||||
|
super(client, width, layout.getContentHeight(), layout.getHeaderHeight(), ITEM_HEIGHT);
|
||||||
|
this.centerListVertically = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int addEntry(SettingsEntry entry) {
|
||||||
|
return super.addEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowWidth() {
|
||||||
|
return ROW_WIDTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SettingsEntry extends ElementListWidget.Entry<SettingsEntry> {
|
||||||
|
private final ClickableWidget left;
|
||||||
|
private final ClickableWidget right;
|
||||||
|
private final List<ClickableWidget> widgets;
|
||||||
|
private final int topPadding;
|
||||||
|
|
||||||
|
SettingsEntry(ClickableWidget left, ClickableWidget right) {
|
||||||
|
this(left, right, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsEntry(ClickableWidget left, ClickableWidget right, int topPadding) {
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
this.topPadding = topPadding;
|
||||||
|
if (left == null && right == null) {
|
||||||
|
this.widgets = List.of();
|
||||||
|
} else if (right == null) {
|
||||||
|
this.widgets = List.of(left);
|
||||||
|
} else {
|
||||||
|
this.widgets = List.of(left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight,
|
||||||
|
int mouseX, int mouseY, boolean hovered, float delta) {
|
||||||
|
if (left == null && right == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (right == null) {
|
||||||
|
if (left == null) return;
|
||||||
|
left.setX(x);
|
||||||
|
left.setY(y + topPadding);
|
||||||
|
left.render(context, mouseX, mouseY, delta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int leftX = x;
|
||||||
|
int rightX = x + COLUMN_WIDTH + COLUMN_GAP;
|
||||||
|
left.setX(leftX);
|
||||||
|
left.setY(y + topPadding);
|
||||||
|
right.setX(rightX);
|
||||||
|
right.setY(y + topPadding);
|
||||||
|
left.render(context, mouseX, mouseY, delta);
|
||||||
|
right.render(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends Element> children() {
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends Selectable> selectableChildren() {
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class SpeedSlider extends SliderWidget {
|
||||||
|
private final String label;
|
||||||
|
private final java.util.function.Consumer<Float> onApply;
|
||||||
|
|
||||||
|
private static float toSpeed(double v) { return (float) (0.2 + v * 2.8); }
|
||||||
|
private static double toValue(float speed) {
|
||||||
|
double v = (speed - 0.2) / 2.8;
|
||||||
|
if (v < 0) v = 0;
|
||||||
|
if (v > 1) v = 1;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedSlider(int width, String label, float speed,
|
||||||
|
java.util.function.Consumer<Float> onApply) {
|
||||||
|
super(0, 0, width, 20, Text.literal(label), toValue(speed));
|
||||||
|
this.label = label;
|
||||||
|
this.onApply = onApply;
|
||||||
|
updateMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateMessage() {
|
||||||
|
float sp = toSpeed(this.value);
|
||||||
|
setMessage(Text.literal(label + ": " + String.format(Locale.ROOT, "%.2f", sp) + "x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void applyValue() {
|
||||||
|
onApply.accept(toSpeed(this.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.straice.smoothdoors.compat;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.ui.SddConfigScreen;
|
||||||
|
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||||
|
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||||
|
|
||||||
|
public class ModMenuIntegration implements ModMenuApi {
|
||||||
|
@Override
|
||||||
|
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||||
|
return parent -> new SddConfigScreen(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.render.block.BlockModelRenderer;
|
||||||
|
import net.minecraft.client.render.model.BlockModelPart;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(BlockModelRenderer.class)
|
||||||
|
public class BlockModelRendererMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "render(Lnet/minecraft/world/BlockRenderView;Ljava/util/List;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZI)V",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$render(BlockRenderView world, List<BlockModelPart> parts, BlockState state, BlockPos pos,
|
||||||
|
MatrixStack matrices, VertexConsumer vertices, boolean cull, int overlay,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderSmooth(Lnet/minecraft/world/BlockRenderView;Ljava/util/List;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZI)V",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderSmooth(BlockRenderView world, List<BlockModelPart> parts, BlockState state, BlockPos pos,
|
||||||
|
MatrixStack matrices, VertexConsumer vertices, boolean cull, int overlay,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderFlat(Lnet/minecraft/world/BlockRenderView;Ljava/util/List;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZI)V",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderFlat(BlockRenderView world, List<BlockModelPart> parts, BlockState state, BlockPos pos,
|
||||||
|
MatrixStack matrices, VertexConsumer vertices, boolean cull, int overlay,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.render.block.BlockRenderManager;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(BlockRenderManager.class)
|
||||||
|
public class BlockRenderManagerMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZLjava/util/List;)V",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderBlock(BlockState state, BlockPos pos, BlockRenderView world,
|
||||||
|
MatrixStack matrices, VertexConsumer vertexConsumer,
|
||||||
|
boolean cull, List<Object> parts,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;Z)Z",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderBlock(BlockState state, BlockPos pos, BlockRenderView world,
|
||||||
|
MatrixStack matrices, VertexConsumer vertexConsumer,
|
||||||
|
boolean cull, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
cir.setReturnValue(false);
|
||||||
|
cir.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.client.render.chunk.ChunkRendererRegion;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(ChunkRendererRegion.class)
|
||||||
|
public class ChunkRendererRegionMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getBlockState", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void sdd$getBlockState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
|
||||||
|
if (SddAnimator.isAnimatingAt(pos)) {
|
||||||
|
cir.setReturnValue(Blocks.AIR.getDefaultState());
|
||||||
|
cir.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ClientWorld.class)
|
||||||
|
public class ClientWorldMixin {
|
||||||
|
|
||||||
|
@Inject(method = "updateListeners", at = @At("TAIL"))
|
||||||
|
private void sdd$updateListeners(BlockPos pos, BlockState oldState, BlockState newState, int flags, CallbackInfo ci) {
|
||||||
|
SddAnimator.onBlockUpdate(pos, oldState, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/client/resources/smooth-double-doors.client.mixins.json
Normal file
14
src/client/resources/smooth-double-doors.client.mixins.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"package": "com.straice.smoothdoors.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"client": [
|
||||||
|
"client.ClientWorldMixin",
|
||||||
|
"client.BlockRenderManagerMixin",
|
||||||
|
"client.BlockModelRendererMixin",
|
||||||
|
"client.ChunkRendererRegionMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/java/com/straice/smoothdoors/SmoothDoubleDoors.java
Normal file
23
src/main/java/com/straice/smoothdoors/SmoothDoubleDoors.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package com.straice.smoothdoors;
|
||||||
|
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
|
||||||
|
public class SmoothDoubleDoors implements ModInitializer {
|
||||||
|
public static final String MOD_ID = "smooth-double-doors";
|
||||||
|
|
||||||
|
// This logger is used to write text to the console and the log file.
|
||||||
|
// It is considered best practice to use your mod id as the logger's name.
|
||||||
|
// That way, it's clear which mod wrote info, warnings, and errors.
|
||||||
|
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
SddConfigManager.load();
|
||||||
|
LOGGER.info("Smooth Double Doors loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/com/straice/smoothdoors/config/SddConfig.java
Normal file
20
src/main/java/com/straice/smoothdoors/config/SddConfig.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.straice.smoothdoors.config;
|
||||||
|
|
||||||
|
public class SddConfig {
|
||||||
|
public boolean connectDoors = true;
|
||||||
|
public boolean redstoneDoubleDoors = true;
|
||||||
|
|
||||||
|
public boolean connectTrapdoors = false;
|
||||||
|
public boolean redstoneDoubleTrapdoors = false;
|
||||||
|
|
||||||
|
public boolean connectFenceGates = false;
|
||||||
|
public boolean redstoneDoubleFenceGates = false;
|
||||||
|
|
||||||
|
public boolean animateDoors = true;
|
||||||
|
public boolean animateTrapdoors = true;
|
||||||
|
public boolean animateFenceGates = true;
|
||||||
|
|
||||||
|
public float doorSpeed = 1.0f;
|
||||||
|
public float trapdoorSpeed = 1.0f;
|
||||||
|
public float fenceGateSpeed = 1.0f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.straice.smoothdoors.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class SddConfigManager {
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final Path PATH = FabricLoader.getInstance().getConfigDir().resolve("smooth-double-doors.json");
|
||||||
|
|
||||||
|
private static SddConfig config = new SddConfig();
|
||||||
|
|
||||||
|
private SddConfigManager() {}
|
||||||
|
|
||||||
|
public static SddConfig get() { return config; }
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
try {
|
||||||
|
if (Files.exists(PATH)) {
|
||||||
|
String json = Files.readString(PATH);
|
||||||
|
SddConfig read = GSON.fromJson(json, SddConfig.class);
|
||||||
|
if (read != null) config = read;
|
||||||
|
} else {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// si falla, usamos defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(PATH.getParent());
|
||||||
|
Files.writeString(PATH, GSON.toJson(config));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.DoorBlock;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.state.property.Properties;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.block.WireOrientation;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(DoorBlock.class)
|
||||||
|
public class DoorBlockMixin {
|
||||||
|
|
||||||
|
@Inject(method = "onUse", at = @At("TAIL"))
|
||||||
|
private void sdd$onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit,
|
||||||
|
CallbackInfoReturnable<ActionResult> cir) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectDoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockPos lower = ConnectedUtil.getDoorLowerPos(pos, state);
|
||||||
|
BlockState lowerNow = world.getBlockState(lower);
|
||||||
|
if (!lowerNow.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = lowerNow.get(Properties.OPEN);
|
||||||
|
BlockPos mateLower = ConnectedUtil.findPairedDoorLower(world, lower, lowerNow);
|
||||||
|
if (mateLower == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setDoorOpen(world, mateLower, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.21.8+: neighborUpdate(..., @Nullable WireOrientation wireOrientation, boolean notify)
|
||||||
|
@Inject(method = "neighborUpdate", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborUpdate(BlockState state, World world, BlockPos pos, Block block,
|
||||||
|
@Nullable WireOrientation wireOrientation, boolean notify, CallbackInfo ci) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectDoors) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleDoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockPos lower = ConnectedUtil.getDoorLowerPos(pos, state);
|
||||||
|
BlockState lowerNow = world.getBlockState(lower);
|
||||||
|
if (!lowerNow.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = lowerNow.get(Properties.OPEN);
|
||||||
|
BlockPos mateLower = ConnectedUtil.findPairedDoorLower(world, lower, lowerNow);
|
||||||
|
if (mateLower == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setDoorOpen(world, mateLower, openNow));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.FenceGateBlock;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.state.property.Properties;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.block.WireOrientation;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(FenceGateBlock.class)
|
||||||
|
public class FenceGateBlockMixin {
|
||||||
|
|
||||||
|
@Inject(method = "onUse", at = @At("TAIL"))
|
||||||
|
private void sdd$onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit,
|
||||||
|
CallbackInfoReturnable<ActionResult> cir) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectFenceGates) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = world.getBlockState(pos);
|
||||||
|
if (!now.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.get(Properties.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedFenceGate(world, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setFenceGateOpen(world, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.21.8+: neighborUpdate(..., @Nullable WireOrientation wireOrientation, boolean notify)
|
||||||
|
@Inject(method = "neighborUpdate", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborUpdate(BlockState state, World world, BlockPos pos, Block block,
|
||||||
|
@Nullable WireOrientation wireOrientation, boolean notify, CallbackInfo ci) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectFenceGates) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleFenceGates) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = world.getBlockState(pos);
|
||||||
|
if (!now.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.get(Properties.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedFenceGate(world, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setFenceGateOpen(world, mate, openNow));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.TrapdoorBlock;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.state.property.Properties;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.block.WireOrientation;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(TrapdoorBlock.class)
|
||||||
|
public class TrapdoorBlockMixin {
|
||||||
|
|
||||||
|
@Inject(method = "onUse", at = @At("TAIL"))
|
||||||
|
private void sdd$onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit,
|
||||||
|
CallbackInfoReturnable<ActionResult> cir) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectTrapdoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = world.getBlockState(pos);
|
||||||
|
if (!now.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.get(Properties.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedTrapdoor(world, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setTrapdoorOpen(world, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.21.8+: neighborUpdate(..., @Nullable WireOrientation wireOrientation, boolean notify)
|
||||||
|
@Inject(method = "neighborUpdate", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborUpdate(BlockState state, World world, BlockPos pos, Block block,
|
||||||
|
@Nullable WireOrientation wireOrientation, boolean notify, CallbackInfo ci) {
|
||||||
|
if (world.isClient) return;
|
||||||
|
if (!SddConfigManager.get().connectTrapdoors) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleTrapdoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = world.getBlockState(pos);
|
||||||
|
if (!now.contains(Properties.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.get(Properties.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedTrapdoor(world, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setTrapdoorOpen(world, mate, openNow));
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/main/java/com/straice/smoothdoors/util/ConnectedUtil.java
Normal file
126
src/main/java/com/straice/smoothdoors/util/ConnectedUtil.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package com.straice.smoothdoors.util;
|
||||||
|
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.DoorBlock;
|
||||||
|
import net.minecraft.block.FenceGateBlock;
|
||||||
|
import net.minecraft.block.TrapdoorBlock;
|
||||||
|
import net.minecraft.block.enums.BlockHalf;
|
||||||
|
import net.minecraft.block.enums.DoorHinge;
|
||||||
|
import net.minecraft.block.enums.DoubleBlockHalf;
|
||||||
|
import net.minecraft.state.property.Properties;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
public final class ConnectedUtil {
|
||||||
|
private ConnectedUtil() {}
|
||||||
|
|
||||||
|
// ===== DOORS =====
|
||||||
|
|
||||||
|
public static BlockPos getDoorLowerPos(BlockPos pos, BlockState state) {
|
||||||
|
DoubleBlockHalf half = state.get(Properties.DOUBLE_BLOCK_HALF);
|
||||||
|
return half == DoubleBlockHalf.LOWER ? pos : pos.down();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockPos findPairedDoorLower(World world, BlockPos lowerPos, BlockState lowerState) {
|
||||||
|
if (!(lowerState.getBlock() instanceof DoorBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = lowerState.get(Properties.HORIZONTAL_FACING);
|
||||||
|
DoorHinge hinge = lowerState.get(Properties.DOOR_HINGE);
|
||||||
|
|
||||||
|
Direction side = (hinge == DoorHinge.LEFT)
|
||||||
|
? facing.rotateYClockwise()
|
||||||
|
: facing.rotateYCounterclockwise();
|
||||||
|
|
||||||
|
BlockPos otherLower = lowerPos.offset(side);
|
||||||
|
BlockState other = world.getBlockState(otherLower);
|
||||||
|
|
||||||
|
if (other.getBlock() != lowerState.getBlock()) return null;
|
||||||
|
if (!(other.getBlock() instanceof DoorBlock)) return null;
|
||||||
|
if (other.get(Properties.DOUBLE_BLOCK_HALF) != DoubleBlockHalf.LOWER) return null;
|
||||||
|
if (other.get(Properties.HORIZONTAL_FACING) != facing) return null;
|
||||||
|
if (other.get(Properties.DOOR_HINGE) == hinge) return null;
|
||||||
|
|
||||||
|
return otherLower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDoorOpen(World world, BlockPos doorLower, boolean open) {
|
||||||
|
BlockState lower = world.getBlockState(doorLower);
|
||||||
|
if (!(lower.getBlock() instanceof DoorBlock)) return;
|
||||||
|
|
||||||
|
BlockPos upperPos = doorLower.up();
|
||||||
|
BlockState upper = world.getBlockState(upperPos);
|
||||||
|
|
||||||
|
world.setBlockState(doorLower, lower.with(Properties.OPEN, open), Block.NOTIFY_LISTENERS);
|
||||||
|
|
||||||
|
if (upper.getBlock() == lower.getBlock()) {
|
||||||
|
world.setBlockState(upperPos, upper.with(Properties.OPEN, open), Block.NOTIFY_LISTENERS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== TRAPDOORS =====
|
||||||
|
|
||||||
|
public static BlockPos findPairedTrapdoor(World world, BlockPos pos, BlockState state) {
|
||||||
|
if (!(state.getBlock() instanceof TrapdoorBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = state.get(Properties.HORIZONTAL_FACING);
|
||||||
|
BlockHalf half = state.get(Properties.BLOCK_HALF);
|
||||||
|
|
||||||
|
BlockPos left = pos.offset(facing.rotateYCounterclockwise());
|
||||||
|
BlockPos right = pos.offset(facing.rotateYClockwise());
|
||||||
|
|
||||||
|
BlockPos mate = matchTrapdoor(world, state, left, facing, half);
|
||||||
|
if (mate != null) return mate;
|
||||||
|
return matchTrapdoor(world, state, right, facing, half);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockPos matchTrapdoor(World world, BlockState a, BlockPos candidate, Direction facing, BlockHalf half) {
|
||||||
|
BlockState b = world.getBlockState(candidate);
|
||||||
|
if (b.getBlock() != a.getBlock()) return null;
|
||||||
|
if (!(b.getBlock() instanceof TrapdoorBlock)) return null;
|
||||||
|
|
||||||
|
if (b.get(Properties.HORIZONTAL_FACING) != facing) return null;
|
||||||
|
if (b.get(Properties.BLOCK_HALF) != half) return null;
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrapdoorOpen(World world, BlockPos pos, boolean open) {
|
||||||
|
BlockState st = world.getBlockState(pos);
|
||||||
|
if (!(st.getBlock() instanceof TrapdoorBlock)) return;
|
||||||
|
|
||||||
|
world.setBlockState(pos, st.with(Properties.OPEN, open), Block.NOTIFY_LISTENERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== FENCE GATES =====
|
||||||
|
|
||||||
|
public static BlockPos findPairedFenceGate(World world, BlockPos pos, BlockState state) {
|
||||||
|
if (!(state.getBlock() instanceof FenceGateBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = state.get(Properties.HORIZONTAL_FACING);
|
||||||
|
|
||||||
|
BlockPos left = pos.offset(facing.rotateYCounterclockwise());
|
||||||
|
BlockPos right = pos.offset(facing.rotateYClockwise());
|
||||||
|
|
||||||
|
BlockPos mate = matchFenceGate(world, state, left, facing);
|
||||||
|
if (mate != null) return mate;
|
||||||
|
return matchFenceGate(world, state, right, facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockPos matchFenceGate(World world, BlockState a, BlockPos candidate, Direction facing) {
|
||||||
|
BlockState b = world.getBlockState(candidate);
|
||||||
|
if (b.getBlock() != a.getBlock()) return null;
|
||||||
|
if (!(b.getBlock() instanceof FenceGateBlock)) return null;
|
||||||
|
|
||||||
|
if (b.get(Properties.HORIZONTAL_FACING) != facing) return null;
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFenceGateOpen(World world, BlockPos pos, boolean open) {
|
||||||
|
BlockState st = world.getBlockState(pos);
|
||||||
|
if (!(st.getBlock() instanceof FenceGateBlock)) return;
|
||||||
|
|
||||||
|
world.setBlockState(pos, st.with(Properties.OPEN, open), Block.NOTIFY_LISTENERS);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/straice/smoothdoors/util/SyncGuard.java
Normal file
16
src/main/java/com/straice/smoothdoors/util/SyncGuard.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.straice.smoothdoors.util;
|
||||||
|
|
||||||
|
public final class SyncGuard {
|
||||||
|
private static final ThreadLocal<Boolean> IN = ThreadLocal.withInitial(() -> false);
|
||||||
|
|
||||||
|
private SyncGuard() {}
|
||||||
|
|
||||||
|
public static boolean isIn() { return IN.get(); }
|
||||||
|
|
||||||
|
public static void run(Runnable r) {
|
||||||
|
if (isIn()) return;
|
||||||
|
IN.set(true);
|
||||||
|
try { r.run(); }
|
||||||
|
finally { IN.set(false); }
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/resources/assets/smooth-double-doors/icon.png
Normal file
BIN
src/main/resources/assets/smooth-double-doors/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 958 KiB |
32
src/main/resources/fabric.mod.json
Normal file
32
src/main/resources/fabric.mod.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "smooth-double-doors",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Smooth Double Doors",
|
||||||
|
"description": "Make double doors, trapdoors, and fence gates open together with fluid smooth animations, fully configurable and texture-pack friendly.",
|
||||||
|
"authors": ["DekinDev"],
|
||||||
|
"contact": {
|
||||||
|
"sources": "https://git.straice.com/DekinDev/Smooth-Double-Doors",
|
||||||
|
"issues": "https://git.straice.com/DekinDev/Smooth-Double-Doors/issues",
|
||||||
|
"homepage": "https://modrinth.com/project/smooth-double-doors"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"icon": "assets/smooth-double-doors/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": ["com.straice.smoothdoors.SmoothDoubleDoors"],
|
||||||
|
"client": ["com.straice.smoothdoors.SmoothDoubleDoorsClient"],
|
||||||
|
"modmenu": ["com.straice.smoothdoors.compat.ModMenuIntegration"]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"smooth-double-doors.mixins.json",
|
||||||
|
"smooth-double-doors.client.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": ">=0.18.4",
|
||||||
|
"minecraft": ">=1.21.8 <=1.21.11",
|
||||||
|
"java": ">=21",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"modmenu": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/resources/smooth-double-doors.mixins.json
Normal file
13
src/main/resources/smooth-double-doors.mixins.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"package": "com.straice.smoothdoors.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"mixins": [
|
||||||
|
"DoorBlockMixin",
|
||||||
|
"TrapdoorBlockMixin",
|
||||||
|
"FenceGateBlockMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user