Compare commits
530 Commits
1.12.2-R0.
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0951bd4c | ||
|
|
7dcfc130d2 | ||
|
|
0e9940771e | ||
|
|
ee8e09fe58 | ||
|
|
b74ec9aa2e | ||
|
|
ea8292aaf9 | ||
|
|
7b47710c42 | ||
|
|
d34531aa84 | ||
|
|
30bb7a43fb | ||
|
|
9a54ba116e | ||
|
|
a3cc630375 | ||
|
|
e5081d4575 | ||
|
|
852acc0d65 | ||
|
|
7e756e786d | ||
|
|
62848df6de | ||
|
|
f5a531a15c | ||
|
|
ba3d1c9672 | ||
|
|
fa08306751 | ||
|
|
9a8d4c35a9 | ||
|
|
adf961666a | ||
|
|
947b22892c | ||
|
|
0d9f04a748 | ||
|
|
26652d43fc | ||
|
|
ac24d10a57 | ||
|
|
f220a6a153 | ||
|
|
233aab099e | ||
|
|
5268e9a55a | ||
|
|
83c30311d0 | ||
|
|
6a7f2b8699 | ||
|
|
cb55572504 | ||
|
|
9d5c535d18 | ||
|
|
b46cddd84d | ||
|
|
936cf720f4 | ||
|
|
9b6412766f | ||
|
|
d847e87c65 | ||
|
|
ff8c0cd3be | ||
|
|
19991463bf | ||
|
|
c2931e362e | ||
|
|
e912498dac | ||
|
|
98a7b4d757 | ||
|
|
62aa216b48 | ||
|
|
1a56f7ac75 | ||
|
|
0080380486 | ||
|
|
e7419af0de | ||
|
|
371817ab85 | ||
|
|
9ad318d4bb | ||
|
|
e066c5e12c | ||
|
|
cfa99ef96e | ||
|
|
03bcac33d7 | ||
|
|
fcbda069fc | ||
|
|
27561af9d8 | ||
|
|
ecb47cc23f | ||
|
|
2fd9d75ba7 | ||
|
|
69c5da47a0 | ||
|
|
c46b459f08 | ||
|
|
a540b150aa | ||
|
|
16eb219b60 | ||
|
|
3dac5cebbf | ||
|
|
78a3fb3aad | ||
|
|
df97aca45f | ||
|
|
a0daa4f40e | ||
|
|
ebf0968fd3 | ||
|
|
a0d41a8dea | ||
|
|
8101d7b1a7 | ||
|
|
9a0558d218 | ||
|
|
6d023d0ff5 | ||
|
|
26d65b8875 | ||
|
|
b65309abd3 | ||
|
|
e07860fb92 | ||
|
|
9491da6b8f | ||
|
|
4d57eb6172 | ||
|
|
2920fa4993 | ||
|
|
48574c2eb4 | ||
|
|
233199054e | ||
|
|
fe6068d6de | ||
|
|
a9a9b933f5 | ||
|
|
d082c42898 | ||
|
|
d784d03289 | ||
|
|
3702d83f80 | ||
|
|
ebd4a75b88 | ||
|
|
d45dde48f1 | ||
|
|
55878409ac | ||
|
|
5f0eca806a | ||
|
|
5e656d02eb | ||
|
|
e407443ca9 | ||
|
|
345faccac7 | ||
|
|
0dcd33f41b | ||
|
|
10a0c82475 | ||
|
|
2871e0dc81 | ||
|
|
15ab29a426 | ||
|
|
93cac1aeea | ||
|
|
55bbbdfbf2 | ||
|
|
01555f49a0 | ||
|
|
4c40903958 | ||
|
|
74d0113b46 | ||
|
|
1db625034e | ||
|
|
6fbd890884 | ||
|
|
3411916593 | ||
|
|
9e2b072bc6 | ||
|
|
cf03617415 | ||
|
|
d2c3477e59 | ||
|
|
6c9c9bef6e | ||
|
|
4a48e19dcc | ||
|
|
0e44c002df | ||
|
|
cbe1830f1c | ||
|
|
c7e7d1a40b | ||
|
|
daf6af0269 | ||
|
|
b510185638 | ||
|
|
0eaeb3b28c | ||
|
|
a2fad07eb6 | ||
|
|
f07072102b | ||
|
|
94264a57a3 | ||
|
|
83f71f673b | ||
|
|
3794ab48c1 | ||
|
|
643f313789 | ||
|
|
573d187e38 | ||
|
|
2e9075e2d7 | ||
|
|
db9cf4e5a2 | ||
|
|
08c6269f3e | ||
|
|
d25176aff1 | ||
|
|
f5fc1e6b64 | ||
|
|
000ea6409b | ||
|
|
925e91c99e | ||
|
|
b1b3efae8d | ||
|
|
a00d3645e1 | ||
|
|
95d3bc3529 | ||
|
|
677914bd39 | ||
|
|
e82e06c760 | ||
|
|
e40656e7f2 | ||
|
|
676173300f | ||
|
|
2d45820922 | ||
|
|
bdbec60f02 | ||
|
|
433e18e7d2 | ||
|
|
21d24bdfa5 | ||
|
|
4eec855840 | ||
|
|
dbe68a55d4 | ||
|
|
08d22009b7 | ||
|
|
0f32cb2ee9 | ||
|
|
fc47e66e4c | ||
|
|
8778323b0c | ||
|
|
8751adbf1b | ||
|
|
742fe4e0a3 | ||
|
|
3d75fe1118 | ||
|
|
a1c3051508 | ||
|
|
da57bfbf57 | ||
|
|
54e05c84f6 | ||
|
|
9fd22502b2 | ||
|
|
5551f505a1 | ||
|
|
8f421ffb2a | ||
|
|
7fe2637206 | ||
|
|
362c9561dd | ||
|
|
fa1cf770f4 | ||
|
|
4758e8aeef | ||
|
|
998b18d2d8 | ||
|
|
b5b7684c58 | ||
|
|
48604bc9a6 | ||
|
|
2f4b0a7a88 | ||
|
|
16a91e1e31 | ||
|
|
d64d8eb5f8 | ||
|
|
e10f2ae716 | ||
|
|
e62e662196 | ||
|
|
6c425fa820 | ||
|
|
29385180ab | ||
|
|
295d11b87a | ||
|
|
fda0839d62 | ||
|
|
6002e2e6d2 | ||
|
|
b23baff1d8 | ||
|
|
05ea8eda44 | ||
|
|
84de7a43fc | ||
|
|
3a53619460 | ||
|
|
fd6f9ef9f3 | ||
|
|
bcea435a88 | ||
|
|
8d669910f6 | ||
|
|
5f3843eb14 | ||
|
|
c407e63047 | ||
|
|
c60d1f672e | ||
|
|
ff6b2d60ab | ||
|
|
1cde35cfc1 | ||
|
|
49cfe2d8e6 | ||
|
|
19f2be2d7d | ||
|
|
8ce60a6575 | ||
|
|
1fffe53720 | ||
|
|
c33f5cea3b | ||
|
|
bc27b6b11d | ||
|
|
ea9589df5c | ||
|
|
53e8b9cf2a | ||
|
|
1d0b02d8d3 | ||
|
|
7436817e08 | ||
|
|
7b6f58c979 | ||
|
|
7f12dd9c28 | ||
|
|
973d4d56c2 | ||
|
|
db31b8edde | ||
|
|
2aabcc3bb1 | ||
|
|
9e83056973 | ||
|
|
7d268c178c | ||
|
|
4ed193b1dd | ||
|
|
1445ea5401 | ||
|
|
950662e652 | ||
|
|
9686940a08 | ||
|
|
46c2fd8790 | ||
|
|
53b7a9095c | ||
|
|
1803ed497f | ||
|
|
9931749781 | ||
|
|
5721388795 | ||
|
|
7e195b8bd9 | ||
|
|
3929f61633 | ||
|
|
d9b608826a | ||
|
|
30f216b3c5 | ||
|
|
663953230d | ||
|
|
18868c715c | ||
|
|
26e0546b50 | ||
|
|
1708888915 | ||
|
|
a4c72a2762 | ||
|
|
a096eeadb1 | ||
|
|
47fc205ba4 | ||
|
|
8a9d3acc05 | ||
|
|
c4db74f13a | ||
|
|
044759f197 | ||
|
|
c120dafe85 | ||
|
|
3cf50c844d | ||
|
|
fbeb9840ea | ||
|
|
82106ee8b6 | ||
|
|
a74aa135ae | ||
|
|
0201722e69 | ||
|
|
cbf71003ff | ||
|
|
90f6d0e9ba | ||
|
|
4290216041 | ||
|
|
33b5d7fb22 | ||
|
|
ac1d99be3b | ||
|
|
d282f46a5b | ||
|
|
a1dee4e1b6 | ||
|
|
e8e6eb230d | ||
|
|
5fd8d633cb | ||
|
|
473190dc17 | ||
|
|
ce64d00b3a | ||
|
|
810c8a5d00 | ||
|
|
768ac2f14c | ||
|
|
183c5fc53d | ||
|
|
5e51b97d27 | ||
|
|
dbd6e66ab9 | ||
|
|
8cb2bc2356 | ||
|
|
48ff1679fe | ||
|
|
8f77cd21b5 | ||
|
|
a825d60641 | ||
|
|
64f74e19a0 | ||
|
|
a741a4336c | ||
|
|
6946210cf9 | ||
|
|
e412d13ccc | ||
|
|
a74603ce85 | ||
|
|
b9a14c436f | ||
|
|
3ff1a9d072 | ||
|
|
0174722008 | ||
|
|
7475c7fb8f | ||
|
|
41da0735ba | ||
|
|
843e612652 | ||
|
|
739614b286 | ||
|
|
04502d021b | ||
|
|
2bc7a3b190 | ||
|
|
1cbd868bdf | ||
|
|
c54d83cedc | ||
|
|
d9040bf6f3 | ||
|
|
a877b1903f | ||
|
|
a768ef41c6 | ||
|
|
22d100f9f4 | ||
|
|
1e57476b41 | ||
|
|
d5e59449fd | ||
|
|
3b20dbd3f8 | ||
|
|
ec977b85e9 | ||
|
|
c22d51f944 | ||
|
|
c05abe18b1 | ||
|
|
da6685793d | ||
|
|
55b7a1bfed | ||
|
|
0f9e51fd0b | ||
|
|
bfca47a782 | ||
|
|
f2563e1f76 | ||
|
|
a5d9181450 | ||
|
|
f1b3273cba | ||
|
|
1028ea8ff8 | ||
|
|
3d562bec84 | ||
|
|
610ec6d52e | ||
|
|
27a7b2a823 | ||
|
|
cb3fe4a4c2 | ||
|
|
b894a5256a | ||
|
|
b99ec14a99 | ||
|
|
4f2510bb51 | ||
|
|
24360a8085 | ||
|
|
367f5e2e87 | ||
|
|
91e0140620 | ||
|
|
24b26ab846 | ||
|
|
c0df425445 | ||
|
|
389c787eb9 | ||
|
|
685b07f51d | ||
|
|
43adb03349 | ||
|
|
d742c096c6 | ||
|
|
8071a603ba | ||
|
|
889b6ea5ba | ||
|
|
2df37be2e2 | ||
|
|
36543cabf5 | ||
|
|
2173b1e2bc | ||
|
|
6e9351caaf | ||
|
|
eb28c5ae38 | ||
|
|
ae6de9567f | ||
|
|
a6c252130f | ||
|
|
5b2147cef5 | ||
|
|
a4836cf014 | ||
|
|
a924263f2f | ||
|
|
eb4ac897c7 | ||
|
|
090ab24a6b | ||
|
|
445f6f185d | ||
|
|
3c198efd87 | ||
|
|
8fa3e82671 | ||
|
|
7381ae8793 | ||
|
|
ac5291d8e4 | ||
|
|
57b84f327b | ||
|
|
4529c0237a | ||
|
|
0dfabac80f | ||
|
|
7c4a81bcb6 | ||
|
|
263cf75f49 | ||
|
|
005b2d2097 | ||
|
|
13b5b7cc13 | ||
|
|
1bc90004ca | ||
|
|
8a41fa0401 | ||
|
|
043b87f4ef | ||
|
|
6da2ca1f67 | ||
|
|
f0044d7d07 | ||
|
|
ea18e73509 | ||
|
|
f24d488601 | ||
|
|
a3501fb8dd | ||
|
|
70b75489f2 | ||
|
|
abe730886a | ||
|
|
645293ed0e | ||
|
|
00859a2e18 | ||
|
|
d643cae29f | ||
|
|
9aa63c56ab | ||
|
|
b06e5f3e13 | ||
|
|
f2a81f8a1e | ||
|
|
7487ac4edf | ||
|
|
07a8521999 | ||
|
|
1879eebd8f | ||
|
|
679236cfb9 | ||
|
|
5fe93610c9 | ||
|
|
f73c20e342 | ||
|
|
8e6000c523 | ||
|
|
2f87a4c54e | ||
|
|
751fcb584d | ||
|
|
10d2d285d9 | ||
|
|
818f5559f7 | ||
|
|
bf7f6fe3bc | ||
|
|
7acc339704 | ||
|
|
f0d17e54e9 | ||
|
|
01c40ed0d3 | ||
|
|
4fdc9f0166 | ||
|
|
3b4926bed2 | ||
|
|
7e0f44f0af | ||
|
|
b41e4fd8c3 | ||
|
|
241a8ffefa | ||
|
|
c2748ea2df | ||
|
|
e563233ff3 | ||
|
|
9a5de6cf31 | ||
|
|
9f7b490f61 | ||
|
|
6dceb465c3 | ||
|
|
17ba164cbc | ||
|
|
bb19cf0f7a | ||
|
|
0a59c8ce5f | ||
|
|
5d5bb381c0 | ||
|
|
05cc09ff51 | ||
|
|
2ba4bc2755 | ||
|
|
9b5b40c002 | ||
|
|
a0545a756d | ||
|
|
6e62515d11 | ||
|
|
ff100c348e | ||
|
|
a25ff5dd93 | ||
|
|
c6dbae3c24 | ||
|
|
b2bafb826f | ||
|
|
fb20bb3113 | ||
|
|
927d946dba | ||
|
|
9ceec10f18 | ||
|
|
74353989e4 | ||
|
|
c2705d4722 | ||
|
|
b743d7dc4c | ||
|
|
601ec9e90d | ||
|
|
6dcd61f2c7 | ||
|
|
c42528f4f1 | ||
|
|
402d9d2536 | ||
|
|
6ba139a83e | ||
|
|
0690cb1c9f | ||
|
|
b8879db641 | ||
|
|
a3cc6062b6 | ||
|
|
dba9fad220 | ||
|
|
af7444df09 | ||
|
|
2def9e628a | ||
|
|
1641f2767f | ||
|
|
f2c15275e9 | ||
|
|
c58612f271 | ||
|
|
4d44c9f8cc | ||
|
|
3d34b1e011 | ||
|
|
032ffea438 | ||
|
|
2b6175edcb | ||
|
|
c06c7e2776 | ||
|
|
af678041f0 | ||
|
|
5c03cad4f2 | ||
|
|
6de709c4c6 | ||
|
|
bca8f20393 | ||
|
|
144c9e08f5 | ||
|
|
bd47cab2ff | ||
|
|
4c7da04d9f | ||
|
|
cf300713b4 | ||
|
|
4c93b2b13a | ||
|
|
78ddbbd7c7 | ||
|
|
ecb28a58a6 | ||
|
|
9e62b22fb8 | ||
|
|
2ace17105f | ||
|
|
528ab01026 | ||
|
|
87583d4e7c | ||
|
|
f77f039dbb | ||
|
|
3fbba5de34 | ||
|
|
79d3980be2 | ||
|
|
280db63b42 | ||
|
|
f93abbf076 | ||
|
|
03a5ef5149 | ||
|
|
58de950203 | ||
|
|
2887399202 | ||
|
|
cbfe35f062 | ||
|
|
1a330a4c07 | ||
|
|
c932a0d1b1 | ||
|
|
515e9d86b9 | ||
|
|
5f917f8253 | ||
|
|
cede9c99fa | ||
|
|
fd733df52a | ||
|
|
5f6671cd50 | ||
|
|
ff00b7556e | ||
|
|
3875266bb7 | ||
|
|
ca8e0d7c0f | ||
|
|
269e1c30f1 | ||
|
|
79d0207801 | ||
|
|
a2c09040b4 | ||
|
|
c50f6f2a68 | ||
|
|
9cfe108024 | ||
|
|
545ce61711 | ||
|
|
e881bb1c23 | ||
|
|
c926121dd6 | ||
|
|
b859bf6b4d | ||
|
|
5113e99853 | ||
|
|
68b4db992f | ||
|
|
2765bb8891 | ||
|
|
067b86b7cc | ||
|
|
08013d2b4a | ||
|
|
a6a51f9805 | ||
|
|
d223e4ad00 | ||
|
|
44d1491789 | ||
|
|
e82a99d4d8 | ||
|
|
5e912befc0 | ||
|
|
45d6b0c072 | ||
|
|
d596840e83 | ||
|
|
9e06a9fe5c | ||
|
|
452dd1f3b5 | ||
|
|
8dbfbd2650 | ||
|
|
fc17d8fdc7 | ||
|
|
bbd1057666 | ||
|
|
671c5fbc49 | ||
|
|
405a299b36 | ||
|
|
8b6de7576a | ||
|
|
f6f2d1121c | ||
|
|
8a6c36ab2c | ||
|
|
4c622207e1 | ||
|
|
8db031c856 | ||
|
|
7c6627edc8 | ||
|
|
343666756c | ||
|
|
99b9880f0e | ||
|
|
022a6468d5 | ||
|
|
9d5d638670 | ||
|
|
4e920243d1 | ||
|
|
239f83e1d3 | ||
|
|
93588656d1 | ||
|
|
8a9ad35a57 | ||
|
|
e4ed177d7b | ||
|
|
fb7bf16869 | ||
|
|
1e93929534 | ||
|
|
30f6e3f56f | ||
|
|
c55568a01c | ||
|
|
04ff47a598 | ||
|
|
fdec006644 | ||
|
|
3eb2067ffa | ||
|
|
b89d955eb5 | ||
|
|
54205afb71 | ||
|
|
711fc08eeb | ||
|
|
e8dfb64f42 | ||
|
|
a78ae42d9c | ||
|
|
f7792c2510 | ||
|
|
9fedd2d94e | ||
|
|
cf62349837 | ||
|
|
1d9f3a0584 | ||
|
|
8925ac73d4 | ||
|
|
5c39b7eabd | ||
|
|
d8f0674b89 | ||
|
|
cda8c79514 | ||
|
|
080b080c15 | ||
|
|
19e1d0d927 | ||
|
|
974cf681d0 | ||
|
|
3e38351ce7 | ||
|
|
dc30e8c8d5 | ||
|
|
427a4152f3 | ||
|
|
68816a0b5a | ||
|
|
f6e54678bb | ||
|
|
aed67213c2 | ||
|
|
9038d2d2b6 | ||
|
|
eb96ab4f5b | ||
|
|
89ca2f2bc5 | ||
|
|
97798e18ce | ||
|
|
db54c87abd | ||
|
|
f45908ae38 | ||
|
|
2b49aba77c | ||
|
|
bc2a639c52 | ||
|
|
3938713380 | ||
|
|
dc10b10979 | ||
|
|
876bf4d7cd | ||
|
|
3ee0111f24 | ||
|
|
42a90440b9 | ||
|
|
b58e98e0de | ||
|
|
dc9c0655de | ||
|
|
c3e9767dbc | ||
|
|
01ecfef964 | ||
|
|
8a7ebe839c | ||
|
|
c11a88d1ba | ||
|
|
9626867217 | ||
|
|
55248e4deb | ||
|
|
095a6e4d0b | ||
|
|
559896e14b | ||
|
|
6855fbe294 | ||
|
|
41d97284be |
50
.circleci/config.yml
Normal file
50
.circleci/config.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/Akarin-project/Akarin
|
||||
parallelism: 1
|
||||
shell: /bin/bash --login
|
||||
environment:
|
||||
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
|
||||
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
|
||||
docker:
|
||||
- image: circleci/build-image:ubuntu-14.04-XXL-upstart-1189-5614f37
|
||||
command: /sbin/init
|
||||
steps:
|
||||
# Machine Setup
|
||||
- checkout
|
||||
# Prepare for artifact
|
||||
- run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS
|
||||
- run:
|
||||
working_directory: ~/Akarin-project/Akarin
|
||||
command: sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java; sudo update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac; echo -e "export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64" >> $BASH_ENV
|
||||
# Dependencies
|
||||
# Restore the dependency cache
|
||||
- restore_cache:
|
||||
keys:
|
||||
# This branch if available
|
||||
- v1-dep-{{ .Branch }}-
|
||||
# Default branch if not
|
||||
- v1-dep-ver/1.12.2-
|
||||
# Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
|
||||
- v1-dep-
|
||||
- run: git config --global user.email "circle@circleci.com"
|
||||
- run: git config --global user.name "CircleCI"
|
||||
- run: chmod +x scripts/inst.sh
|
||||
- run: ./scripts/inst.sh --setup --remote
|
||||
# Save dependency cache
|
||||
- save_cache:
|
||||
key: v1-dep-{{ .Branch }}-{{ epoch }}
|
||||
paths:
|
||||
- ~/.m2
|
||||
# Test
|
||||
- run: yes|cp -rf ./akarin-*.jar $CIRCLE_ARTIFACTS
|
||||
# Teardown
|
||||
# Save test results
|
||||
- store_test_results:
|
||||
path: /tmp/circleci-test-results
|
||||
# Save artifacts
|
||||
- store_artifacts:
|
||||
path: /tmp/circleci-artifacts
|
||||
- store_artifacts:
|
||||
path: /tmp/circleci-test-results
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "work/Paper"]
|
||||
path = work/Paper
|
||||
url = https://github.com/Akarin-project/Paper.git
|
||||
url = https://github.com/Akarin-project/Paper.git
|
||||
|
||||
24
Jenkinsfile
vendored
Normal file
24
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Init Submodules') {
|
||||
steps {
|
||||
sh 'git submodule update --init --recursive'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'chmod +x scripts/inst.sh'
|
||||
sh './scripts/inst.sh --setup --fast --remote'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Archive') {
|
||||
steps {
|
||||
archiveArtifacts(artifacts: '*.jar', fingerprint: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,17 @@ As such, Akarin is licensed under the
|
||||
[GNU General Public License version 3](licenses/GPL.md); as it inherits it from Paper,
|
||||
who in turn inherits it from the original Spigot projects.
|
||||
|
||||
Any author who is _not_ listed below should be presumed to have released their work
|
||||
Any author who is _not_ listed below should be presumed to have their work released
|
||||
under the original [GPL](licenses/GPL.md) license.
|
||||
|
||||
In the interest of promoting a better Minecraft platform for everyone, contributors
|
||||
may choose to release their code under the more permissive [MIT License](licenses/MIT.md).
|
||||
|
||||
The authors listed below have chosen to release their code under that more permissive
|
||||
The authors listed below have chosen to release their code under the more permissive
|
||||
[MIT License](licenses/MIT.md). Any contributor who wants their name added below
|
||||
should submit a pull request to this project to add their name.
|
||||
should submit a Pull Request to this project and add their name.
|
||||
|
||||
```text
|
||||
Sotr <kira@kira.moe>
|
||||
```
|
||||
MatrixTunnel <https://github.com/MatrixTunnel>
|
||||
```
|
||||
|
||||
53
README.md
53
README.md
@@ -1,29 +1,31 @@
|
||||
# <img src="https://i.loli.net/2018/05/17/5afd869c443ef.png" alt="Akarin Face" align="right">Akarin
|
||||
[](http://ci.ilummc.com/job/Akarin/)
|
||||
[](https://bstats.org/plugin/bukkit/Torch)
|
||||
[](https://akarin.io)
|
||||
[](https://discord.gg/fw2pJAj)
|
||||
[](https://bstats.org/plugin/bukkit/Torch)
|
||||
|
||||
Akarin is currently **under heavy development** and contributions are welcome!
|
||||
|
||||
Introduction
|
||||
---
|
||||
> Akarin is a powerful server software form the 'new dimension', formerly known as [Torch](https://github.com/Akarin-project/Torch).
|
||||
> Akarin is a powerful server software from the 'new dimension', formerly known as Torch.
|
||||
|
||||
As a [Paper](https://github.com/PaperMC/Paper) fork, it supports almost all plugins that [Spigot](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/spigot/browse) can use.
|
||||
As a [Paper](https://github.com/PaperMC/Paper) fork, it should support almost all plugins that work on [Spigot](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/spigot/browse).
|
||||
|
||||
It has a few key goals:
|
||||
* **Open Access** - Make more game mechanism configurable.
|
||||
* **Bedrock** - Safety and stable is important for a server.
|
||||
* **Fast** - Simplify the logic and import the multi-thread compute.
|
||||
Our project has a few key goals:
|
||||
|
||||
Akarin is **under heavy development** yet, contribution is welcome and run a test before putting into production.
|
||||
* **Open Access** - Make more game mechanics configurable.
|
||||
* **Bedrock** - Make the server more safe and stable.
|
||||
* **Fast** - Simplify the logic and implement multi-threaded computing.
|
||||
|
||||
*Issues and Pull Requests will be labeled accordingly*
|
||||
|
||||
Get Akarin
|
||||
---
|
||||
---
|
||||
### Download
|
||||
#### Recommended Sites
|
||||
+ [ **Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/master) - Checkout the 'Artifacts' tab of the latest build *Login required*
|
||||
+ [ **Jenkins**](http://ci.ilummc.com/job/Akarin/) - *Kudos to [Izzel_Aliz](https://github.com/IzzelAliz)*
|
||||
#### Recommended
|
||||
+ [**Jenkins**](http://ci.josephworks.net/job/Akarin/job/ver%252F1.12.2/) - Kudos to [JosephWorks](https://github.com/josephworks)
|
||||
|
||||
*Contact me via the email below or open an [Issue](https://github.com/Akarin-project/akarin/issues) if you want to add your website here*
|
||||
*Open an [Issue](https://github.com/Akarin-project/Akarin/issues) or a [Pull Request](https://github.com/Akarin-project/Akarin/pulls) if you want to add your website here*
|
||||
|
||||
### Build
|
||||
#### Requirements
|
||||
@@ -34,20 +36,23 @@ Get Akarin
|
||||
```sh
|
||||
./scripts/inst.sh --setup --fast
|
||||
```
|
||||
*For non-modification compile, add `--fast` option to skip the test is recommended.*
|
||||
*Futhermore, if your machine have a insufficient memory, you may add `--remote` option to avoid decompile locally.*
|
||||
|
||||
Demonstration servers
|
||||
**Notes**
|
||||
* You must use `--setup` at least once to deploy necessary dependencies otherwise some imports cannot be organized.
|
||||
* For non-modified projects, it is recommended to add the `--fast` option to skip any tests.
|
||||
* If your machine has insufficient memory, you may want to add the `--remote` option to avoid decompiling locally.
|
||||
|
||||
Demo Servers
|
||||
---
|
||||
+ **demo.akarin.io**
|
||||
* `demo.akarin.io` (official)
|
||||
* `omc.hk` (auth required)
|
||||
|
||||
*Contact me via the email below or open an [Issue](https://github.com/Akarin-project/akarin/issues) if you want to add your server here*
|
||||
*Open an [Issue](https://github.com/Akarin-project/Akarin/issues) or a [Pull Request](https://github.com/Akarin-project/Akarin/pulls) if you want to add your website here*
|
||||
|
||||
Contributing
|
||||
---
|
||||
* Feel free to open an [Issue](https://github.com/Akarin-project/akarin/issues) if you have any problem with Akarin.
|
||||
* [Pull Request](https://github.com/Akarin-project/akarin/pulls) is welcomed, Akarin use [Mixin](https://github.com/SpongePowered/Mixin) to modify the code, you can checkout `sources` folder to see them. Moreover, add your name to the [LICENSE](https://github.com/Akarin-project/Akarin/blob/master/LICENSE.md) if you want to publish your code under the [MIT License](https://github.com/Akarin-project/Akarin/blob/master/licenses/MIT.md).
|
||||
* If you want to join the [Akarin-project](https://github.com/Akarin-project) team, you can send an email to `kira@kira.moe` with your experience and necessary information. Besides, welcome to join our [TIM Group](https://jq.qq.com/?_wv=1027&k=59q2kV4) to chat *(Chinese)*.
|
||||
* Note that you must `--setup` at least once to deploy necessary dependency otherwise some imports cannot be organized.
|
||||
* Akarin uses [Mixin](https://github.com/SpongePowered/Mixin) to modify the code. You can checkout the `sources` folder to see more.
|
||||
* Add your name to the [LICENSE](https://github.com/Akarin-project/Akarin/blob/master/LICENSE.md) if you want to publish your code under the [MIT License](https://github.com/Akarin-project/Akarin/blob/master/licenses/MIT.md).
|
||||
* If you want to join the [Akarin-project](https://github.com/Akarin-project) team, you can [send](mailto://kira@kira.moe) us an email with your experience and necessary information.
|
||||
|
||||

|
||||

|
||||
|
||||
16
circle.yml
16
circle.yml
@@ -1,16 +0,0 @@
|
||||
machine:
|
||||
java:
|
||||
version: openjdk8
|
||||
|
||||
dependencies:
|
||||
cache-directories:
|
||||
- "/home/ubuntu/Akarin/work/Paper/work/Minecraft"
|
||||
override:
|
||||
- git config --global user.email "circle@circleci.com"
|
||||
- git config --global user.name "CircleCI"
|
||||
- chmod +x scripts/inst.sh
|
||||
- ./scripts/inst.sh --setup --remote
|
||||
|
||||
test:
|
||||
post:
|
||||
- yes|cp -rf ./akarin-1.12.2.jar $CIRCLE_ARTIFACTS
|
||||
62
removed/com/destroystokyo/paper/antixray/DataBitsReader.java
Normal file
62
removed/com/destroystokyo/paper/antixray/DataBitsReader.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.destroystokyo.paper.antixray;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class DataBitsReader {
|
||||
|
||||
private ByteBuf dataBits; // Akarin
|
||||
private int bitsPerValue;
|
||||
private int mask;
|
||||
private int longInDataBitsIndex;
|
||||
private int bitInLongIndex;
|
||||
private long current;
|
||||
|
||||
public void setDataBits(ByteBuf dataBits) { // Akarin
|
||||
this.dataBits = dataBits;
|
||||
}
|
||||
|
||||
public void setBitsPerValue(int bitsPerValue) {
|
||||
this.bitsPerValue = bitsPerValue;
|
||||
mask = (1 << bitsPerValue) - 1;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.longInDataBitsIndex = index;
|
||||
bitInLongIndex = 0;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (dataBits.capacity() > longInDataBitsIndex + 7) { // Akarin
|
||||
// Akarin start
|
||||
dataBits.getLong(longInDataBitsIndex);
|
||||
/*
|
||||
current = ((((long) dataBits[longInDataBitsIndex]) << 56)
|
||||
| (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48)
|
||||
| (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40)
|
||||
| (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32)
|
||||
| (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24)
|
||||
| (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16)
|
||||
| (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8)
|
||||
| (((long) dataBits[longInDataBitsIndex + 7] & 0xff)));
|
||||
*/ // Akarin end
|
||||
}
|
||||
}
|
||||
|
||||
public int read() {
|
||||
int value = (int) (current >>> bitInLongIndex) & mask;
|
||||
bitInLongIndex += bitsPerValue;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
|
||||
if (bitInLongIndex > 0) {
|
||||
value |= current << bitsPerValue - bitInLongIndex & mask;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
94
removed/com/destroystokyo/paper/antixray/DataBitsWriter.java
Normal file
94
removed/com/destroystokyo/paper/antixray/DataBitsWriter.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package com.destroystokyo.paper.antixray;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class DataBitsWriter {
|
||||
|
||||
private ByteBuf dataBits; // Akarin
|
||||
private int bitsPerValue;
|
||||
private long mask;
|
||||
private int longInDataBitsIndex;
|
||||
private int bitInLongIndex;
|
||||
private long current;
|
||||
private boolean dirty;
|
||||
|
||||
public void setDataBits(ByteBuf dataBits) { // Akarin
|
||||
this.dataBits = dataBits;
|
||||
}
|
||||
|
||||
public void setBitsPerValue(int bitsPerValue) {
|
||||
this.bitsPerValue = bitsPerValue;
|
||||
mask = (1 << bitsPerValue) - 1;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.longInDataBitsIndex = index;
|
||||
bitInLongIndex = 0;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (dataBits.capacity() > longInDataBitsIndex + 7) { // Akarin
|
||||
// Akarin start
|
||||
current = dataBits.getLong(longInDataBitsIndex);
|
||||
/*
|
||||
current = ((((long) dataBits[longInDataBitsIndex]) << 56)
|
||||
| (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48)
|
||||
| (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40)
|
||||
| (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32)
|
||||
| (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24)
|
||||
| (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16)
|
||||
| (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8)
|
||||
| (((long) dataBits[longInDataBitsIndex + 7] & 0xff)));
|
||||
*/ // Akarin end
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
if (dirty && dataBits.capacity() > longInDataBitsIndex + 7) { // Akarin
|
||||
// Akarin start
|
||||
dataBits.setLong(longInDataBitsIndex, current);
|
||||
/*
|
||||
dataBits[longInDataBitsIndex] = (byte) (current >> 56 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 1] = (byte) (current >> 48 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 2] = (byte) (current >> 40 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 3] = (byte) (current >> 32 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 4] = (byte) (current >> 24 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 5] = (byte) (current >> 16 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 6] = (byte) (current >> 8 & 0xff);
|
||||
dataBits[longInDataBitsIndex + 7] = (byte) (current & 0xff);
|
||||
*/ // Akarin end
|
||||
}
|
||||
}
|
||||
|
||||
public void write(int value) {
|
||||
current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
|
||||
dirty = true;
|
||||
bitInLongIndex += bitsPerValue;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
finish();
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
|
||||
if (bitInLongIndex > 0) {
|
||||
current = current & ~(mask >>> bitsPerValue - bitInLongIndex) | (value & mask) >>> bitsPerValue - bitInLongIndex;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void skip() {
|
||||
bitInLongIndex += bitsPerValue;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
finish();
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.destroystokyo.paper.antixray;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.DataPalette;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.PacketPlayOutMapChunk;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) byte[] -> ByteBuf (compatibility)
|
||||
*/
|
||||
public class PacketPlayOutMapChunkInfo {
|
||||
|
||||
private final PacketPlayOutMapChunk packetPlayOutMapChunk;
|
||||
private final Chunk chunk;
|
||||
private final int chunkSectionSelector;
|
||||
private ByteBuf data; // Akarin
|
||||
private final int[] bitsPerValue = new int[16];
|
||||
private final DataPalette[] dataPalettes = new DataPalette[16];
|
||||
private final int[] dataBitsIndexes = new int[16];
|
||||
private final IBlockData[][] predefinedBlockData = new IBlockData[16][];
|
||||
|
||||
public PacketPlayOutMapChunkInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) {
|
||||
this.packetPlayOutMapChunk = packetPlayOutMapChunk;
|
||||
this.chunk = chunk;
|
||||
this.chunkSectionSelector = chunkSectionSelector;
|
||||
}
|
||||
|
||||
public PacketPlayOutMapChunk getPacketPlayOutMapChunk() {
|
||||
return packetPlayOutMapChunk;
|
||||
}
|
||||
|
||||
public Chunk getChunk() {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public int getChunkSectionSelector() {
|
||||
return chunkSectionSelector;
|
||||
}
|
||||
|
||||
public ByteBuf getData() { // Akarin
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ByteBuf data) { // Akarin
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getBitsPerValue(int chunkSectionIndex) {
|
||||
return bitsPerValue[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setBitsPerValue(int chunkSectionIndex, int bitsPerValue) {
|
||||
this.bitsPerValue[chunkSectionIndex] = bitsPerValue;
|
||||
}
|
||||
|
||||
public DataPalette getDataPalette(int chunkSectionIndex) {
|
||||
return dataPalettes[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setDataPalette(int chunkSectionIndex, DataPalette dataPalette) {
|
||||
dataPalettes[chunkSectionIndex] = dataPalette;
|
||||
}
|
||||
|
||||
public int getDataBitsIndex(int chunkSectionIndex) {
|
||||
return dataBitsIndexes[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setDataBitsIndex(int chunkSectionIndex, int dataBitsIndex) {
|
||||
dataBitsIndexes[chunkSectionIndex] = dataBitsIndex;
|
||||
}
|
||||
|
||||
public IBlockData[] getPredefinedBlockData(int chunkSectionIndex) {
|
||||
return predefinedBlockData[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setPredefinedBlockData(int chunkSectionIndex, IBlockData[] predefinedBlockData) {
|
||||
this.predefinedBlockData[chunkSectionIndex] = predefinedBlockData;
|
||||
}
|
||||
|
||||
public boolean isWritten(int chunkSectionIndex) {
|
||||
return bitsPerValue[chunkSectionIndex] != 0;
|
||||
}
|
||||
}
|
||||
193
removed/net/minecraft/server/PacketPlayOutMapChunk.java
Normal file
193
removed/net/minecraft/server/PacketPlayOutMapChunk.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
// Paper start
|
||||
import com.destroystokyo.paper.antixray.PacketPlayOutMapChunkInfo; // Anti-Xray
|
||||
// Paper end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) WrappedByteBuf -> ByteBuf (compatibility)
|
||||
*/
|
||||
public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
||||
|
||||
private int a;
|
||||
private int b;
|
||||
private int c;
|
||||
private ByteBuf d; // Akarin - byte[] -> ByteBuf
|
||||
private List<NBTTagCompound> e;
|
||||
private boolean f;
|
||||
private volatile boolean ready = false; // Paper - Async-Anti-Xray - Ready flag for the network manager
|
||||
|
||||
// Paper start - Async-Anti-Xray - Set the ready flag to true
|
||||
public PacketPlayOutMapChunk() {
|
||||
this.ready = true;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
public PacketPlayOutMapChunk(Chunk chunk, int i) {
|
||||
PacketPlayOutMapChunkInfo packetPlayOutMapChunkInfo = chunk.world.chunkPacketBlockController.getPacketPlayOutMapChunkInfo(this, chunk, i); // Paper - Anti-Xray - Add chunk packet info
|
||||
this.a = chunk.locX;
|
||||
this.b = chunk.locZ;
|
||||
this.f = i == '\uffff';
|
||||
boolean flag = chunk.getWorld().worldProvider.m();
|
||||
|
||||
this.d = allocateBuffer(this.a(chunk, flag, i)); // Akarin
|
||||
|
||||
// Paper start - Anti-Xray - Add chunk packet info
|
||||
if (packetPlayOutMapChunkInfo != null) {
|
||||
packetPlayOutMapChunkInfo.setData(this.d);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
this.c = this.writeChunk(new PacketDataSerializer(this.d), chunk, flag, i, packetPlayOutMapChunkInfo); // Paper - Anti-Xray - Add chunk packet info // Akarin
|
||||
this.e = Lists.newArrayList();
|
||||
Iterator iterator = chunk.getTileEntities().entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entry entry = (Entry) iterator.next();
|
||||
BlockPosition blockposition = (BlockPosition) entry.getKey();
|
||||
TileEntity tileentity = (TileEntity) entry.getValue();
|
||||
int j = blockposition.getY() >> 4;
|
||||
|
||||
if (this.e() || (i & 1 << j) != 0) {
|
||||
NBTTagCompound nbttagcompound = tileentity.d();
|
||||
|
||||
this.e.add(nbttagcompound);
|
||||
}
|
||||
}
|
||||
|
||||
chunk.world.chunkPacketBlockController.modifyBlocks(this, packetPlayOutMapChunkInfo); // Paper - Anti-Xray - Modify blocks
|
||||
}
|
||||
|
||||
// Paper start - Async-Anti-Xray - Getter and Setter for the ready flag
|
||||
public boolean isReady() {
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
public void setReady(boolean ready) {
|
||||
this.ready = ready;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
public void a(PacketDataSerializer packetdataserializer) throws IOException {
|
||||
this.a = packetdataserializer.readInt();
|
||||
this.b = packetdataserializer.readInt();
|
||||
this.f = packetdataserializer.readBoolean();
|
||||
this.c = packetdataserializer.g();
|
||||
int i = packetdataserializer.g();
|
||||
|
||||
if (i > 2097152) {
|
||||
throw new RuntimeException("Chunk Packet trying to allocate too much memory on read.");
|
||||
} else {
|
||||
this.d = Unpooled.buffer(i); // Akarin
|
||||
packetdataserializer.readBytes(this.d);
|
||||
int j = packetdataserializer.g();
|
||||
|
||||
this.e = Lists.newArrayList();
|
||||
|
||||
for (int k = 0; k < j; ++k) {
|
||||
this.e.add(packetdataserializer.j());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void b(PacketDataSerializer packetdataserializer) throws IOException {
|
||||
packetdataserializer.writeInt(this.a);
|
||||
packetdataserializer.writeInt(this.b);
|
||||
packetdataserializer.writeBoolean(this.f);
|
||||
packetdataserializer.d(this.c);
|
||||
packetdataserializer.d(this.d.capacity()); // Akarin
|
||||
packetdataserializer.writeBytes(this.d.array()); // Akarin
|
||||
packetdataserializer.d(this.e.size());
|
||||
Iterator iterator = this.e.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
NBTTagCompound nbttagcompound = (NBTTagCompound) iterator.next();
|
||||
|
||||
packetdataserializer.a(nbttagcompound);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(PacketListenerPlayOut packetlistenerplayout) {
|
||||
packetlistenerplayout.a(this);
|
||||
}
|
||||
|
||||
private ByteBuf g() { return allocateBuffer(-1); } // Akarin
|
||||
private ByteBuf allocateBuffer(int expectedCapacity) { // Akarin - added argument
|
||||
ByteBuf bytebuf = expectedCapacity == -1 ? Unpooled.buffer() : Unpooled.buffer(expectedCapacity); // Akarin
|
||||
|
||||
bytebuf.writerIndex(0);
|
||||
return bytebuf;
|
||||
}
|
||||
|
||||
// Paper start - Anti-Xray - Support default method
|
||||
public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, boolean writeSkyLightArray, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector); } // OBFHELPER
|
||||
public int a(PacketDataSerializer packetdataserializer, Chunk chunk, boolean flag, int i) {
|
||||
return this.a(packetdataserializer, chunk, flag, i, null);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, boolean writeSkyLightArray, int chunkSectionSelector, PacketPlayOutMapChunkInfo packetPlayOutMapChunkInfo) { return this.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, packetPlayOutMapChunkInfo); } // Paper - Anti-Xray - OBFHELPER
|
||||
public int a(PacketDataSerializer packetdataserializer, Chunk chunk, boolean flag, int i, PacketPlayOutMapChunkInfo packetPlayOutMapChunkInfo) { // Paper - Anti-Xray - Add chunk packet info
|
||||
int j = 0;
|
||||
ChunkSection[] achunksection = chunk.getSections();
|
||||
int k = 0;
|
||||
|
||||
for (int l = achunksection.length; k < l; ++k) {
|
||||
ChunkSection chunksection = achunksection[k];
|
||||
|
||||
if (chunksection != Chunk.a && (!this.e() || !chunksection.a()) && (i & 1 << k) != 0) {
|
||||
j |= 1 << k;
|
||||
chunksection.getBlocks().writeBlocks(packetdataserializer, packetPlayOutMapChunkInfo, k); // Paper - Anti-Xray - Add chunk packet info
|
||||
packetdataserializer.writeBytes(chunksection.getEmittedLightArray().asBytes());
|
||||
if (flag) {
|
||||
packetdataserializer.writeBytes(chunksection.getSkyLightArray().asBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.e()) {
|
||||
packetdataserializer.writeBytes(chunk.getBiomeIndex());
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
protected int a(Chunk chunk, boolean flag, int i) {
|
||||
int j = 0;
|
||||
ChunkSection[] achunksection = chunk.getSections();
|
||||
int k = 0;
|
||||
|
||||
for (int l = achunksection.length; k < l; ++k) {
|
||||
ChunkSection chunksection = achunksection[k];
|
||||
|
||||
if (chunksection != Chunk.a && (!this.e() || !chunksection.a()) && (i & 1 << k) != 0) {
|
||||
j += chunksection.getBlocks().a();
|
||||
j += chunksection.getEmittedLightArray().asBytes().length;
|
||||
if (flag) {
|
||||
j += chunksection.getSkyLightArray().asBytes().length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.e()) {
|
||||
j += chunk.getBiomeIndex().length;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
public boolean e() {
|
||||
return this.f;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ if [ "$2" == "--setup" ] || [ "$3" == "--setup" ] || [ "$4" == "--setup" ]; then
|
||||
if [ -d "Minecraft" ]; then
|
||||
rm Minecraft/ -r
|
||||
fi
|
||||
git clone https://github.com/Akarin-project/Minecraft.git
|
||||
git clone https://github.com/LegacyGamerHD/Minecraft.git
|
||||
fi
|
||||
|
||||
cd "$paperbasedir"
|
||||
./paper patch
|
||||
./paper jar
|
||||
)
|
||||
fi
|
||||
|
||||
@@ -51,4 +51,4 @@ echo "[Akarin] Ready to build"
|
||||
echo "[Akarin] Migrated final jar to $basedir/akarin-$minecraftversion.jar"
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
4
scripts/inst.sh
Normal file → Executable file
4
scripts/inst.sh
Normal file → Executable file
@@ -4,9 +4,9 @@
|
||||
set -e
|
||||
basedir="$pwd"
|
||||
|
||||
(git submodule update --init --remote && chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
|
||||
(chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
|
||||
echo "Failed to build Akarin"
|
||||
exit 1
|
||||
) || exit 1
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
133
sources/pom.xml
133
sources/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>akarin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.12.2-R0.2.1-RELEASE</version>
|
||||
<version>1.12.2-R0.4-SNAPSHOT</version>
|
||||
<name>Akarin</name>
|
||||
<url>https://github.com/Akarin-project/Akarin</url>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.24.Final</version>
|
||||
<version>4.1.78.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -45,6 +45,12 @@
|
||||
<version>${minecraft.version}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>minecraft-server</artifactId>
|
||||
<version>${minecraft.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
@@ -60,7 +66,7 @@
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.45</version>
|
||||
<version>8.0.28</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -69,16 +75,15 @@
|
||||
<version>3.0.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.minecrell</groupId>
|
||||
<artifactId>terminalconsoleappender</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<version>4.5.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -91,7 +96,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.8.1</version>
|
||||
<version>2.17.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -99,13 +104,21 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<version>2.8.1</version>
|
||||
<version>2.17.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-iostreams</artifactId>
|
||||
<version>2.8.1</version>
|
||||
<version>2.17.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper - Async loggers -->
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- testing -->
|
||||
@@ -121,33 +134,34 @@
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Akarin -->
|
||||
<dependency>
|
||||
<groupId>io.akarin</groupId>
|
||||
<artifactId>legacylauncher</artifactId>
|
||||
<version>1.20</version>
|
||||
<version>1.26</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>mixin</artifactId>
|
||||
<version>0.7.8-SNAPSHOT</version>
|
||||
<version>0.7.11-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.nallar.whocalled</groupId>
|
||||
<artifactId>WhoCalled</artifactId>
|
||||
<version>1.1</version>
|
||||
<groupId>com.googlecode.concurrent-locks</groupId>
|
||||
<artifactId>concurrent-locks</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>2.9.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigotmc-public</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>akarin-repo</id>
|
||||
<url>https://raw.githubusercontent.com/Akarin-project/akarin-repo/master/repository</url>
|
||||
<id>elmakers-repo</id>
|
||||
<url>http://maven.elmakers.com/repository/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spongepowered-repo</id>
|
||||
@@ -157,12 +171,36 @@
|
||||
<id>nallar-repo</id>
|
||||
<url>http://repo.nallar.me/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>josephworks</id>
|
||||
<url>http://repo.josephworks.net/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype-nexusg</id>
|
||||
<url>https://oss.sonatype.org/content/repositories</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spigotmc-public</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
<id>paper</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
@@ -177,7 +215,7 @@
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<outputPrefix>git-Akarin-</outputPrefix>
|
||||
<scmDirectory>..</scmDirectory>
|
||||
<scmDirectory>../..</scmDirectory> <!-- Akarin -->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -203,6 +241,7 @@
|
||||
<Specification-Title>Bukkit</Specification-Title>
|
||||
<Specification-Version>${api.version}</Specification-Version>
|
||||
<Specification-Vendor>Bukkit Team</Specification-Vendor>
|
||||
<Multi-Release>true</Multi-Release> <!-- Paper start - update log4j -->
|
||||
</manifestEntries>
|
||||
<manifestSections>
|
||||
<manifestSection>
|
||||
@@ -230,7 +269,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -239,6 +278,25 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation> <!-- Paper -->
|
||||
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>org.spigotmc:minecraft-server:**</artifact>
|
||||
<excludes>
|
||||
<exclude>io/netty/**</exclude>
|
||||
<exclude>org/apache/logging/log4j/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
|
||||
<relocations>
|
||||
<!-- Paper - Workaround for hardcoded path lookup in dependency, easier than forking it - GH-189 -->
|
||||
<!--<relocation>-->
|
||||
@@ -270,27 +328,16 @@
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/services/java.sql.Driver</resource>
|
||||
</transformer>
|
||||
<transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer" />
|
||||
<transformer implementation="io.github.edwgiz.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" />
|
||||
</transformers>
|
||||
<!-- Akarin - Avoid signature failure -->
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.edwgiz</groupId>
|
||||
<artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
|
||||
<version>2.8.1</version>
|
||||
<groupId>io.github.edwgiz</groupId>
|
||||
<artifactId>log4j-maven-shade-plugin-extensions</artifactId>
|
||||
<version>2.17.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
@@ -298,6 +345,12 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>org/spigotmc/CaseInsensitiveHashingStrategy.java</exclude>
|
||||
<exclude>org/spigotmc/CaseInsensitiveMap.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
128
sources/src/main/java/co/aikar/timings/MinecraftTimings.java
Normal file
128
sources/src/main/java/co/aikar/timings/MinecraftTimings.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package co.aikar.timings;
|
||||
|
||||
import com.google.common.collect.MapMaker;
|
||||
import net.minecraft.server.*;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import org.bukkit.craftbukkit.scheduler.CraftTask;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class MinecraftTimings {
|
||||
|
||||
public static final Timing playerListTimer = Timings.ofSafe("Player List");
|
||||
public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions");
|
||||
public static final Timing connectionTimer = Timings.ofSafe("Connection Handler");
|
||||
public static final Timing tickablesTimer = Timings.ofSafe("Tickables");
|
||||
public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler");
|
||||
public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler");
|
||||
public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending");
|
||||
public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing");
|
||||
public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick");
|
||||
public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update");
|
||||
public static final Timing serverCommandTimer = Timings.ofSafe("Server Command");
|
||||
public static final Timing savePlayers = Timings.ofSafe("Save Players");
|
||||
|
||||
public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity");
|
||||
public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity");
|
||||
public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing");
|
||||
public static final Timing scheduledBlocksTimer = Timings.ofSafe("## Scheduled Blocks");
|
||||
public static final Timing structureGenerationTimer = Timings.ofSafe("Structure Generation");
|
||||
|
||||
public static final Timing processQueueTimer = Timings.ofSafe("processQueue");
|
||||
|
||||
public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand");
|
||||
|
||||
public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck");
|
||||
|
||||
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
|
||||
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
|
||||
|
||||
private static final Map<Class<? extends Runnable>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
private MinecraftTimings() {}
|
||||
|
||||
/**
|
||||
* Gets a timer associated with a plugins tasks.
|
||||
* @param bukkitTask
|
||||
* @param period
|
||||
* @return
|
||||
*/
|
||||
public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) {
|
||||
if (!bukkitTask.isSync()) {
|
||||
return NullTimingHandler.NULL;
|
||||
}
|
||||
Plugin plugin;
|
||||
|
||||
Runnable task = ((CraftTask) bukkitTask).task;
|
||||
|
||||
final Class<? extends Runnable> taskClass = task.getClass();
|
||||
if (bukkitTask.getOwner() != null) {
|
||||
plugin = bukkitTask.getOwner();
|
||||
} else {
|
||||
plugin = TimingsManager.getPluginByClassloader(taskClass);
|
||||
}
|
||||
|
||||
final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz ->
|
||||
clazz.isAnonymousClass() || clazz.isLocalClass()
|
||||
? clazz.getName()
|
||||
: clazz.getCanonicalName());
|
||||
|
||||
StringBuilder name = new StringBuilder(64);
|
||||
name.append("Task: ").append(taskname);
|
||||
if (period > 0) {
|
||||
name.append(" (interval:").append(period).append(")");
|
||||
} else {
|
||||
name.append(" (Single)");
|
||||
}
|
||||
|
||||
if (plugin == null) {
|
||||
return Timings.ofSafe(null, name.toString());
|
||||
}
|
||||
|
||||
return Timings.ofSafe(plugin, name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a named timer for the specified entity type to track type specific timings.
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public static Timing getEntityTimings(Entity entity) {
|
||||
String entityType = entity.getClass().getName();
|
||||
return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType, tickEntityTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a named timer for the specified tile entity type to track type specific timings.
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public static Timing getTileEntityTimings(TileEntity entity) {
|
||||
String entityType = entity.getClass().getName();
|
||||
return Timings.ofSafe("Minecraft", "## tickTileEntity - " + entityType, tickTileEntityTimer);
|
||||
}
|
||||
public static Timing getCancelTasksTimer() {
|
||||
return Timings.ofSafe("Cancel Tasks");
|
||||
}
|
||||
public static Timing getCancelTasksTimer(Plugin plugin) {
|
||||
return Timings.ofSafe(plugin, "Cancel Tasks");
|
||||
}
|
||||
|
||||
public static void stopServer() {
|
||||
TimingsManager.stopServer();
|
||||
}
|
||||
|
||||
public static Timing getBlockTiming(Block block) {
|
||||
return Timings.ofSafe("## Scheduled Block: " + block.getName(), scheduledBlocksTimer);
|
||||
}
|
||||
|
||||
public static Timing getStructureTiming(StructureGenerator structureGenerator) {
|
||||
return Timings.ofSafe("Structure Generator - " + structureGenerator.getName(), structureGenerationTimer);
|
||||
}
|
||||
|
||||
public static Timing getPacketTiming(Packet packet) {
|
||||
return Timings.ofSafe("## Packet - " + packet.getClass().getSimpleName(), packetProcessTimer);
|
||||
}
|
||||
}
|
||||
131
sources/src/main/java/co/aikar/timings/TimedChunkGenerator.java
Normal file
131
sources/src/main/java/co/aikar/timings/TimedChunkGenerator.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2014-2016 Daniel Ennis <http://aikar.co>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package co.aikar.timings;
|
||||
|
||||
import net.minecraft.server.BiomeBase.BiomeMeta;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumCreatureType;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.WorldServer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.craftbukkit.generator.InternalChunkGenerator;
|
||||
import org.bukkit.generator.BlockPopulator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class TimedChunkGenerator extends InternalChunkGenerator {
|
||||
private final WorldServer world;
|
||||
private final InternalChunkGenerator timedGenerator;
|
||||
|
||||
public TimedChunkGenerator(WorldServer worldServer, InternalChunkGenerator gen) {
|
||||
world = worldServer;
|
||||
timedGenerator = gen;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public byte[] generate(org.bukkit.World world, Random random, int x, int z) {
|
||||
return timedGenerator.generate(world, random, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public short[][] generateExtBlockSections(org.bukkit.World world, Random random, int x, int z,
|
||||
BiomeGrid biomes) {
|
||||
return timedGenerator.generateExtBlockSections(world, random, x, z, biomes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public byte[][] generateBlockSections(org.bukkit.World world, Random random, int x, int z,
|
||||
BiomeGrid biomes) {
|
||||
return timedGenerator.generateBlockSections(world, random, x, z, biomes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkData generateChunkData(org.bukkit.World world, Random random, int x, int z, BiomeGrid biome) {
|
||||
return timedGenerator.generateChunkData(world, random, x, z, biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSpawn(org.bukkit.World world, int x, int z) {
|
||||
return timedGenerator.canSpawn(world, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BlockPopulator> getDefaultPopulators(org.bukkit.World world) {
|
||||
return timedGenerator.getDefaultPopulators(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getFixedSpawnLocation(org.bukkit.World world, Random random) {
|
||||
return timedGenerator.getFixedSpawnLocation(world, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getOrCreateChunk(int i, int j) {
|
||||
try (Timing ignored = world.timings.chunkGeneration.startTiming()) {
|
||||
return timedGenerator.getOrCreateChunk(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recreateStructures(int i, int j) {
|
||||
try (Timing ignored = world.timings.syncChunkLoadStructuresTimer.startTiming()) {
|
||||
timedGenerator.recreateStructures(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean a(Chunk chunk, int i, int j) {
|
||||
return timedGenerator.a(chunk, i, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BiomeMeta> getMobsFor(EnumCreatureType enumcreaturetype, BlockPosition blockposition) {
|
||||
return timedGenerator.getMobsFor(enumcreaturetype, blockposition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BlockPosition findNearestMapFeature(World world, String s, BlockPosition blockposition, boolean flag) {
|
||||
return timedGenerator.findNearestMapFeature(world, s, blockposition, flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recreateStructures(Chunk chunk, int i, int j) {
|
||||
try (Timing ignored = world.timings.syncChunkLoadStructuresTimer.startTiming()) {
|
||||
timedGenerator.recreateStructures(chunk, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean a(World world, String s, BlockPosition blockPosition) {
|
||||
return timedGenerator.a(world, s, blockPosition);
|
||||
}
|
||||
}
|
||||
@@ -24,23 +24,26 @@
|
||||
package co.aikar.timings;
|
||||
|
||||
import co.aikar.util.LoadingIntMap;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.Akari.AssignableThread;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Add volatile to fields<br>
|
||||
* @author cakoyo
|
||||
*/
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
class TimingHandler implements Timing {
|
||||
String name;
|
||||
private static AtomicInteger idPool = new AtomicInteger(1);
|
||||
static Deque<TimingHandler> TIMING_STACK = new ArrayDeque<>();
|
||||
final int id = idPool.getAndIncrement();
|
||||
|
||||
private static int idPool = 1;
|
||||
final int id = idPool++;
|
||||
|
||||
final String name;
|
||||
final TimingIdentifier identifier;
|
||||
private final boolean verbose;
|
||||
|
||||
private final Int2ObjectOpenHashMap<TimingData> children = new LoadingIntMap<>(TimingData::new);
|
||||
@@ -48,22 +51,15 @@ class TimingHandler implements Timing {
|
||||
final TimingData record;
|
||||
private final TimingHandler groupHandler;
|
||||
|
||||
private volatile long start = 0; // Akarin - volatile
|
||||
private volatile int timingDepth = 0; // Akarin - volatile
|
||||
private long start = 0;
|
||||
private int timingDepth = 0;
|
||||
private boolean added;
|
||||
private boolean timed;
|
||||
private boolean enabled;
|
||||
private TimingHandler parent;
|
||||
|
||||
TimingHandler(TimingIdentifier id) {
|
||||
if (id.name.startsWith("##")) {
|
||||
verbose = true;
|
||||
this.name = id.name.substring(3);
|
||||
} else {
|
||||
this.name = id.name;
|
||||
verbose = false;
|
||||
}
|
||||
|
||||
this.identifier = id;
|
||||
this.verbose = id.name.startsWith("##");
|
||||
this.record = new TimingData(this.id);
|
||||
this.groupHandler = id.groupHandler;
|
||||
|
||||
@@ -88,59 +84,61 @@ class TimingHandler implements Timing {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
startTiming();
|
||||
}
|
||||
return this;
|
||||
startTiming();
|
||||
return (Timing) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void stopTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
stopTiming();
|
||||
if (Akari.isPrimaryThread(false)) {
|
||||
stopTiming(true); // Avoid twice thread check
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTiming() {
|
||||
if (enabled && ++timingDepth == 1) {
|
||||
if (enabled && Bukkit.isPrimaryThread() && ++timingDepth == 1) {
|
||||
start = System.nanoTime();
|
||||
parent = TimingsManager.CURRENT;
|
||||
TimingsManager.CURRENT = this;
|
||||
TIMING_STACK.addLast(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopTiming() {
|
||||
if (enabled && --timingDepth == 0 && start != 0) {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
stopTiming(false);
|
||||
}
|
||||
|
||||
public void stopTiming(long start) {
|
||||
if (enabled) addDiff(System.nanoTime() - start);
|
||||
}
|
||||
|
||||
public void stopTiming(boolean alreadySync) {
|
||||
if (!enabled) return;
|
||||
if (!alreadySync) {
|
||||
Thread curThread = Thread.currentThread();
|
||||
if (curThread.getClass() == AssignableThread.class) return;
|
||||
if (curThread != MinecraftServer.getServer().primaryThread) {
|
||||
if (AkarinGlobalConfig.silentAsyncTimings) return;
|
||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||
new Throwable().printStackTrace();
|
||||
start = 0;
|
||||
return;
|
||||
Thread.dumpStack();
|
||||
}
|
||||
}
|
||||
|
||||
// Main thread ensured
|
||||
if (--timingDepth == 0 && start != 0) {
|
||||
addDiff(System.nanoTime() - start);
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
if (enabled && timingDepth > 0) {
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
public final void abort() {
|
||||
|
||||
}
|
||||
void addDiff(long diff) {
|
||||
if (TimingsManager.CURRENT == this) {
|
||||
TimingsManager.CURRENT = parent;
|
||||
if (parent != null) {
|
||||
parent.children.get(id).add(diff);
|
||||
}
|
||||
if (this != null) {
|
||||
this.children.get(id).add(diff);
|
||||
}
|
||||
|
||||
record.add(diff);
|
||||
if (!added) {
|
||||
added = true;
|
||||
@@ -148,15 +146,29 @@ class TimingHandler implements Timing {
|
||||
TimingsManager.HANDLERS.add(this);
|
||||
}
|
||||
if (groupHandler != null) {
|
||||
groupHandler.addDiff(diff);
|
||||
groupHandler.addDiff(diff, this);
|
||||
groupHandler.children.get(id).add(diff);
|
||||
}
|
||||
}
|
||||
void addDiff(long diff, TimingHandler parent) {
|
||||
if (parent != null) {
|
||||
parent.children.get(id).add(diff);
|
||||
}
|
||||
|
||||
record.add(diff);
|
||||
if (!added) {
|
||||
added = true;
|
||||
timed = true;
|
||||
TimingsManager.HANDLERS.add(this);
|
||||
}
|
||||
if (groupHandler != null) {
|
||||
groupHandler.addDiff(diff, parent);
|
||||
groupHandler.children.get(id).add(diff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this timer, setting all values to zero.
|
||||
*
|
||||
* @param full
|
||||
*/
|
||||
void reset(boolean full) {
|
||||
record.reset();
|
||||
@@ -186,8 +198,7 @@ class TimingHandler implements Timing {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is simply for the Closeable interface so it can be used with
|
||||
* try-with-resources ()
|
||||
* This is simply for the Closeable interface so it can be used with try-with-resources ()
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
103
sources/src/main/java/co/aikar/timings/WorldTimingsHandler.java
Normal file
103
sources/src/main/java/co/aikar/timings/WorldTimingsHandler.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package co.aikar.timings;
|
||||
|
||||
import net.minecraft.server.World;
|
||||
|
||||
/**
|
||||
* Set of timers per world, to track world specific timings.
|
||||
*/
|
||||
public class WorldTimingsHandler {
|
||||
public final Timing mobSpawn;
|
||||
public final Timing doChunkUnload;
|
||||
public final Timing doPortalForcer;
|
||||
public final Timing scheduledBlocks;
|
||||
public final Timing scheduledBlocksCleanup;
|
||||
public final Timing scheduledBlocksTicking;
|
||||
public final Timing chunkTicks;
|
||||
public final Timing lightChunk;
|
||||
public final Timing chunkTicksBlocks;
|
||||
public final Timing doVillages;
|
||||
public final Timing doChunkMap;
|
||||
public final Timing doChunkMapUpdate;
|
||||
public final Timing doChunkMapToUpdate;
|
||||
public final Timing doChunkMapSortMissing;
|
||||
public final Timing doChunkMapSortSendToPlayers;
|
||||
public final Timing doChunkMapPlayersNeedingChunks;
|
||||
public final Timing doChunkMapPendingSendToPlayers;
|
||||
public final Timing doChunkMapUnloadChunks;
|
||||
public final Timing doChunkGC;
|
||||
public final Timing doSounds;
|
||||
public final Timing entityRemoval;
|
||||
public final Timing entityTick;
|
||||
public final Timing tileEntityTick;
|
||||
public final Timing tileEntityPending;
|
||||
public final Timing tracker1;
|
||||
public final Timing tracker2;
|
||||
public final Timing doTick;
|
||||
public final Timing tickEntities;
|
||||
|
||||
public final Timing syncChunkLoadTimer;
|
||||
public final Timing syncChunkLoadDataTimer;
|
||||
public final Timing syncChunkLoadStructuresTimer;
|
||||
public final Timing syncChunkLoadPostTimer;
|
||||
public final Timing syncChunkLoadNBTTimer;
|
||||
public final Timing syncChunkLoadPopulateNeighbors;
|
||||
public final Timing chunkGeneration;
|
||||
public final Timing chunkIOStage1;
|
||||
public final Timing chunkIOStage2;
|
||||
public final Timing worldSave;
|
||||
public final Timing worldSaveChunks;
|
||||
public final Timing worldSaveLevel;
|
||||
public final Timing chunkSaveData;
|
||||
|
||||
public final Timing lightingQueueTimer;
|
||||
|
||||
public WorldTimingsHandler(World server) {
|
||||
String name = server.worldData.getName() +" - ";
|
||||
|
||||
mobSpawn = Timings.ofSafe(name + "mobSpawn");
|
||||
doChunkUnload = Timings.ofSafe(name + "doChunkUnload");
|
||||
scheduledBlocks = Timings.ofSafe(name + "Scheduled Blocks");
|
||||
scheduledBlocksCleanup = Timings.ofSafe(name + "Scheduled Blocks - Cleanup");
|
||||
scheduledBlocksTicking = Timings.ofSafe(name + "Scheduled Blocks - Ticking");
|
||||
chunkTicks = Timings.ofSafe(name + "Chunk Ticks");
|
||||
lightChunk = Timings.ofSafe(name + "Light Chunk");
|
||||
chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks");
|
||||
doVillages = Timings.ofSafe(name + "doVillages");
|
||||
doChunkMap = Timings.ofSafe(name + "doChunkMap");
|
||||
doChunkMapUpdate = Timings.ofSafe(name + "doChunkMap - Update");
|
||||
doChunkMapToUpdate = Timings.ofSafe(name + "doChunkMap - To Update");
|
||||
doChunkMapSortMissing = Timings.ofSafe(name + "doChunkMap - Sort Missing");
|
||||
doChunkMapSortSendToPlayers = Timings.ofSafe(name + "doChunkMap - Sort Send To Players");
|
||||
doChunkMapPlayersNeedingChunks = Timings.ofSafe(name + "doChunkMap - Players Needing Chunks");
|
||||
doChunkMapPendingSendToPlayers = Timings.ofSafe(name + "doChunkMap - Pending Send To Players");
|
||||
doChunkMapUnloadChunks = Timings.ofSafe(name + "doChunkMap - Unload Chunks");
|
||||
doSounds = Timings.ofSafe(name + "doSounds");
|
||||
doChunkGC = Timings.ofSafe(name + "doChunkGC");
|
||||
doPortalForcer = Timings.ofSafe(name + "doPortalForcer");
|
||||
entityTick = Timings.ofSafe(name + "entityTick");
|
||||
entityRemoval = Timings.ofSafe(name + "entityRemoval");
|
||||
tileEntityTick = Timings.ofSafe(name + "tileEntityTick");
|
||||
tileEntityPending = Timings.ofSafe(name + "tileEntityPending");
|
||||
|
||||
syncChunkLoadTimer = Timings.ofSafe(name + "syncChunkLoad");
|
||||
syncChunkLoadDataTimer = Timings.ofSafe(name + "syncChunkLoad - Data");
|
||||
syncChunkLoadStructuresTimer = Timings.ofSafe(name + "chunkLoad - recreateStructures");
|
||||
syncChunkLoadPostTimer = Timings.ofSafe(name + "chunkLoad - Post");
|
||||
syncChunkLoadNBTTimer = Timings.ofSafe(name + "chunkLoad - NBT");
|
||||
syncChunkLoadPopulateNeighbors = Timings.ofSafe(name + "chunkLoad - Populate Neighbors");
|
||||
chunkGeneration = Timings.ofSafe(name + "chunkGeneration");
|
||||
chunkIOStage1 = Timings.ofSafe(name + "ChunkIO Stage 1 - DiskIO");
|
||||
chunkIOStage2 = Timings.ofSafe(name + "ChunkIO Stage 2 - Post Load");
|
||||
worldSave = Timings.ofSafe(name + "World Save");
|
||||
worldSaveLevel = Timings.ofSafe(name + "World Save - Level");
|
||||
worldSaveChunks = Timings.ofSafe(name + "World Save - Chunks");
|
||||
chunkSaveData = Timings.ofSafe(name + "Chunk Save - Data");
|
||||
|
||||
tracker1 = Timings.ofSafe(name + "tracker stage 1");
|
||||
tracker2 = Timings.ofSafe(name + "tracker stage 2");
|
||||
doTick = Timings.ofSafe(name + "doTick");
|
||||
tickEntities = Timings.ofSafe(name + "tickEntities");
|
||||
|
||||
lightingQueueTimer = Timings.ofSafe(name + "Lighting Queue");
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package io.akarin.api;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.entity.Minecart;
|
||||
|
||||
import com.google.common.collect.Queues;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import co.aikar.timings.Timings;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
public abstract class Akari {
|
||||
/**
|
||||
* A common logger used by mixin classes
|
||||
*/
|
||||
public final static Logger logger = LogManager.getLogger("Akarin");
|
||||
|
||||
/**
|
||||
* Temporarily disable desync timings error, moreover it's worthless to trace async operation
|
||||
*/
|
||||
public static volatile boolean silentTiming;
|
||||
|
||||
/**
|
||||
* A common thread pool factory
|
||||
*/
|
||||
public static final ThreadFactory STAGE_FACTORY = new ThreadFactoryBuilder().setNameFormat("Akarin Schedule Thread - %1$d").build();
|
||||
|
||||
/**
|
||||
* Main thread callback tasks
|
||||
*/
|
||||
public static final Queue<Runnable> callbackQueue = Queues.newConcurrentLinkedQueue();
|
||||
|
||||
/**
|
||||
* A common tick pool
|
||||
*/
|
||||
public static final ExecutorCompletionService<?> STAGE_TICK = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor(Akari.STAGE_FACTORY));
|
||||
|
||||
public static volatile boolean mayMock;
|
||||
|
||||
public static boolean isPrimaryThread() {
|
||||
return Thread.currentThread().equals(MinecraftServer.getServer().primaryThread);
|
||||
}
|
||||
|
||||
/*
|
||||
* The unsafe
|
||||
*/
|
||||
public final static sun.misc.Unsafe UNSAFE = getUnsafe();
|
||||
|
||||
private static sun.misc.Unsafe getUnsafe() {
|
||||
try {
|
||||
Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
return (sun.misc.Unsafe) theUnsafe.get(null);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Timings
|
||||
*/
|
||||
public final static Timing worldTiming = getTiming("Akarin - World");
|
||||
|
||||
public final static Timing callbackTiming = getTiming("Akarin - Callback");
|
||||
|
||||
private static Timing getTiming(String name) {
|
||||
try {
|
||||
Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class);
|
||||
ofSafe.setAccessible(true);
|
||||
return (Timing) ofSafe.invoke(null, name);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
sources/src/main/java/io/akarin/api/internal/Akari.java
Normal file
161
sources/src/main/java/io/akarin/api/internal/Akari.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package io.akarin.api.internal;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.google.common.collect.Queues;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import co.aikar.timings.Timings;
|
||||
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||
import io.akarin.api.internal.Akari.TimingSignal;
|
||||
import io.akarin.api.internal.utils.ReentrantSpinningLock;
|
||||
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
|
||||
import io.akarin.api.internal.utils.thread.SuspendableThreadPoolExecutor;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public abstract class Akari {
|
||||
/**
|
||||
* A common logger used by mixin classes
|
||||
*/
|
||||
public final static Logger logger = LogManager.getLogger("Akarin");
|
||||
|
||||
/**
|
||||
* A common thread pool factory
|
||||
*/
|
||||
public static final ThreadFactory STAGE_FACTORY = new ThreadFactoryBuilder().setNameFormat("Akarin Parallel Registry Thread - %1$d").build();
|
||||
|
||||
/**
|
||||
* Main thread callback tasks
|
||||
*/
|
||||
public static final Queue<Runnable> callbackQueue = Queues.newConcurrentLinkedQueue();
|
||||
|
||||
public static class AssignableThread extends Thread {
|
||||
public AssignableThread(Runnable run) {
|
||||
super(run);
|
||||
}
|
||||
public AssignableThread() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AssignableFactory implements ThreadFactory {
|
||||
private final String threadName;
|
||||
private int threadNumber;
|
||||
|
||||
public AssignableFactory(String name) {
|
||||
threadName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable run) {
|
||||
Thread thread = new AssignableThread(run);
|
||||
thread.setName(StringUtils.replaceChars(threadName, "$", String.valueOf(threadNumber++)));
|
||||
thread.setPriority(AkarinGlobalConfig.primaryThreadPriority); // Fair
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimingSignal {
|
||||
public final World tickedWorld;
|
||||
public final boolean isEntities;
|
||||
|
||||
public TimingSignal(World world, boolean entities) {
|
||||
tickedWorld = world;
|
||||
isEntities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
public static SuspendableExecutorCompletionService<TimingSignal> STAGE_TICK;
|
||||
|
||||
static {
|
||||
resizeTickExecutors(3);
|
||||
}
|
||||
|
||||
public static void resizeTickExecutors(int worlds) {
|
||||
int parallelism;
|
||||
switch (AkarinGlobalConfig.parallelMode) {
|
||||
case -1:
|
||||
return;
|
||||
case 0:
|
||||
parallelism = 2;
|
||||
break;
|
||||
case 1:
|
||||
parallelism = worlds + 1;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
parallelism = worlds * 2;
|
||||
break;
|
||||
}
|
||||
STAGE_TICK = new SuspendableExecutorCompletionService<>(new SuspendableThreadPoolExecutor(parallelism, parallelism,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new AssignableFactory("Akarin Parallel Ticking Thread - $")));
|
||||
}
|
||||
|
||||
public static boolean isPrimaryThread() {
|
||||
return isPrimaryThread(true);
|
||||
}
|
||||
|
||||
public static boolean isPrimaryThread(boolean assign) {
|
||||
Thread current = Thread.currentThread();
|
||||
return current == MinecraftServer.getServer().primaryThread || (assign ? (current.getClass() == AssignableThread.class) : false);
|
||||
}
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
|
||||
/*
|
||||
* The unsafe
|
||||
*/
|
||||
public final static sun.misc.Unsafe UNSAFE = getUnsafe();
|
||||
|
||||
private static sun.misc.Unsafe getUnsafe() {
|
||||
try {
|
||||
Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
return (sun.misc.Unsafe) theUnsafe.get(null);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String serverVersion = Akari.class.getPackage().getImplementationVersion();
|
||||
|
||||
public static String getServerVersion() {
|
||||
return serverVersion + " (MC: " + MinecraftServer.getServer().getVersion() + ")";
|
||||
}
|
||||
|
||||
/*
|
||||
* Timings
|
||||
*/
|
||||
public final static Timing worldTiming = getTiming("Akarin - Full World Tick");
|
||||
|
||||
public final static Timing callbackTiming = getTiming("Akarin - Callback Queue");
|
||||
|
||||
private static Timing getTiming(String name) {
|
||||
try {
|
||||
Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class);
|
||||
ofSafe.setAccessible(true);
|
||||
return (Timing) ofSafe.invoke(null, name);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.akarin.api;
|
||||
package io.akarin.api.internal;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
public interface IMixinRealTimeTicking {
|
||||
|
||||
long getRealTimeTicks();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public interface IMixinWorldServer {
|
||||
public Object lock();
|
||||
public Random rand();
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
* at http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
package io.akarin.api;
|
||||
package io.akarin.api.internal.utils;
|
||||
|
||||
import java.util.AbstractQueue;
|
||||
import java.util.ArrayList;
|
||||
@@ -45,6 +45,8 @@ import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
/**
|
||||
* An unbounded thread-safe {@linkplain Queue queue} based on linked nodes.
|
||||
* This queue orders elements FIFO (first-in-first-out).
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.akarin.api.internal.utils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ReentrantSpinningLock {
|
||||
/*
|
||||
* Impl Note:
|
||||
* A write lock can reentrant as a read lock, while a
|
||||
* read lock is not allowed to reentrant as a write lock.
|
||||
* READ LOCK IS UNTESTED, USE WITH CATION.
|
||||
*/
|
||||
private final AtomicBoolean writeLocked = new AtomicBoolean(false);
|
||||
|
||||
// --------- Thread local restricted fields ---------
|
||||
private long heldThreadId = 0;
|
||||
private int reentrantLocks = 0;
|
||||
|
||||
/**
|
||||
* Lock as a typical reentrant write lock
|
||||
*/
|
||||
public void lock() {
|
||||
long currentThreadId = Thread.currentThread().getId();
|
||||
if (heldThreadId == currentThreadId) {
|
||||
reentrantLocks++;
|
||||
} else {
|
||||
while (!writeLocked.compareAndSet(false, true)) ; // In case acquire one lock concurrently
|
||||
heldThreadId = currentThreadId;
|
||||
}
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
if (reentrantLocks == 0) {
|
||||
heldThreadId = 0;
|
||||
//if (readerThreads.get() == 0 || readerThreads.getAndDecrement() == 1) { // Micro-optimization: this saves one subtract
|
||||
writeLocked.set(false);
|
||||
//}
|
||||
} else {
|
||||
--reentrantLocks;
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicInteger readerThreads = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Lock as a typical reentrant read lock
|
||||
*/
|
||||
@Deprecated
|
||||
public void lockWeak() {
|
||||
long currentThreadId = Thread.currentThread().getId();
|
||||
if (heldThreadId == currentThreadId) {
|
||||
reentrantLocks++;
|
||||
} else {
|
||||
if (readerThreads.get() == 0) {
|
||||
while (!writeLocked.compareAndSet(false, true)) ; // Block future write lock
|
||||
}
|
||||
heldThreadId = currentThreadId;
|
||||
readerThreads.getAndIncrement(); // Micro-optimization: this saves one plus
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void unlockWeak() {
|
||||
if (reentrantLocks == 0) {
|
||||
heldThreadId = 0;
|
||||
writeLocked.set(false);
|
||||
} else {
|
||||
--reentrantLocks;
|
||||
}
|
||||
}
|
||||
|
||||
// --------- Wrappers to allow typical usages ---------
|
||||
private SpinningWriteLock wrappedWriteLock = new SpinningWriteLock();
|
||||
private SpinningReadLock wrappedReadLock = new SpinningReadLock();
|
||||
|
||||
public class SpinningWriteLock {
|
||||
public void lock() {
|
||||
lock();
|
||||
}
|
||||
public void unlock() {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public class SpinningReadLock {
|
||||
public void lock() {
|
||||
lockWeak();
|
||||
}
|
||||
public void unlock() {
|
||||
unlockWeak();
|
||||
}
|
||||
}
|
||||
|
||||
public SpinningWriteLock writeLock() {
|
||||
return wrappedWriteLock;
|
||||
}
|
||||
|
||||
public SpinningReadLock readLock() {
|
||||
return wrappedReadLock;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.akarin.api;
|
||||
package io.akarin.api.internal.utils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.ObjectOutputStream;
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
Written in 2015 by Sebastiano Vigna (vigna@acm.org)
|
||||
|
||||
To the extent possible under law, the author has dedicated all copyright
|
||||
and related and neighboring rights to this software to the public domain
|
||||
worldwide. This software is distributed without any warranty.
|
||||
|
||||
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
|
||||
package io.akarin.api.internal.utils.random;
|
||||
|
||||
/**
|
||||
* This is a SplittableRandom-style generator, meant to have a tiny state
|
||||
* that permits storing many different generators with low overhead.
|
||||
* It should be rather fast, though no guarantees can be made on all hardware.
|
||||
* <br>
|
||||
* Benchmarking on a Windows laptop with an i7-4700MQ processor running OpenJDK 8
|
||||
* reports generation of 64-bit random long output as 17.8x faster than generating
|
||||
* an equivalent number of random longs with java.util.Random, and generation of
|
||||
* 32-bit random int output as 9.8x faster. Specifically, generating 1 billion longs
|
||||
* took about 1.28 nanoseconds per long (1.277 seconds for the whole group) with
|
||||
* LightRNG, while java.util.Random (which is meant to produce int, to be fair) took
|
||||
* about 22.8 nanoseconds per long (22.797 seconds for the whole group). XorRNG
|
||||
* appears to be occasionally faster on int output than LightRNG, but it isn't clear
|
||||
* why or what causes that (JIT or GC internals, possibly). XorRNG is slightly
|
||||
* slower at generating 64-bit random data, including long and double, but not by
|
||||
* a significant degree (a multiplier between 0.9 and 1.2 times). The only deciding
|
||||
* factor then is state size, where LightRNG is as small as possible for any JVM
|
||||
* object with even a single field: 16 bytes (on a 64-bit JVM; 8-byte objects with
|
||||
* 4 bytes or less of non-static members may be possible on 32-bit JVMs but I can't
|
||||
* find anything confirming that guess).
|
||||
* <br>
|
||||
* So yes, this should be very fast, and with only a single long used per LightRNG,
|
||||
* it is about as memory-efficient as these generators get.
|
||||
* <br>
|
||||
* Written in 2015 by Sebastiano Vigna (vigna@acm.org)
|
||||
* @author Sebastiano Vigna
|
||||
* @author Tommy Ettinger
|
||||
*/
|
||||
public class LightRNG implements RandomnessSource, StatefulRandomness
|
||||
{
|
||||
/** 2 raised to the 53, - 1. */
|
||||
private static final long DOUBLE_MASK = ( 1L << 53 ) - 1;
|
||||
/** 2 raised to the -53. */
|
||||
private static final double NORM_53 = 1. / ( 1L << 53 );
|
||||
/** 2 raised to the 24, -1. */
|
||||
private static final long FLOAT_MASK = ( 1L << 24 ) - 1;
|
||||
/** 2 raised to the -24. */
|
||||
private static final double NORM_24 = 1. / ( 1L << 24 );
|
||||
|
||||
private static final long serialVersionUID = -374415589203474497L;
|
||||
|
||||
public long state; /* The state can be seeded with any value. */
|
||||
|
||||
/** Creates a new generator seeded using Math.random. */
|
||||
public LightRNG() {
|
||||
this((long) Math.floor(Math.random() * Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public LightRNG( final long seed ) {
|
||||
setSeed(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next( int bits ) {
|
||||
return (int)( nextLong() & ( 1L << bits ) - 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return any long, positive or negative, of any size permissible in a 64-bit signed integer.
|
||||
* @return any long, all 64 bits are random
|
||||
*/
|
||||
@Override
|
||||
public long nextLong() {
|
||||
long z = ( state += 0x9E3779B97F4A7C15L );
|
||||
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
|
||||
return z ^ (z >>> 31);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the
|
||||
* copy, both will generate the same sequence of random numbers from the point copy() was called. This just need to
|
||||
* copy the state so it isn't shared, usually, and produce a new value with the same exact state.
|
||||
*
|
||||
* @return a copy of this RandomnessSource
|
||||
*/
|
||||
@Override
|
||||
public RandomnessSource copy() {
|
||||
return new LightRNG(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return any int, positive or negative, of any size permissible in a 32-bit signed integer.
|
||||
* @return any int, all 32 bits are random
|
||||
*/
|
||||
public int nextInt() {
|
||||
return (int)nextLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclusive on the upper bound. The lower bound is 0.
|
||||
* @param bound the upper bound; should be positive
|
||||
* @return a random int less than n and at least equal to 0
|
||||
*/
|
||||
public int nextInt( final int bound ) {
|
||||
if ( bound <= 0 ) return 0;
|
||||
int threshold = (0x7fffffff - bound + 1) % bound;
|
||||
for (;;) {
|
||||
int bits = (int)(nextLong() & 0x7fffffff);
|
||||
if (bits >= threshold)
|
||||
return bits % bound;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Inclusive lower, exclusive upper.
|
||||
* @param lower the lower bound, inclusive, can be positive or negative
|
||||
* @param upper the upper bound, exclusive, should be positive, must be greater than lower
|
||||
* @return a random int at least equal to lower and less than upper
|
||||
*/
|
||||
public int nextInt( final int lower, final int upper ) {
|
||||
if ( upper - lower <= 0 ) throw new IllegalArgumentException("Upper bound must be greater than lower bound");
|
||||
return lower + nextInt(upper - lower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclusive on the upper bound. The lower bound is 0.
|
||||
* @param bound the upper bound; should be positive
|
||||
* @return a random long less than n
|
||||
*/
|
||||
public long nextLong( final long bound ) {
|
||||
if ( bound <= 0 ) return 0;
|
||||
long threshold = (0x7fffffffffffffffL - bound + 1) % bound;
|
||||
for (;;) {
|
||||
long bits = nextLong() & 0x7fffffffffffffffL;
|
||||
if (bits >= threshold)
|
||||
return bits % bound;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive lower, exclusive upper.
|
||||
* @param lower the lower bound, inclusive, can be positive or negative
|
||||
* @param upper the upper bound, exclusive, should be positive, must be greater than lower
|
||||
* @return a random long at least equal to lower and less than upper
|
||||
*/
|
||||
public long nextLong( final long lower, final long upper ) {
|
||||
if ( upper - lower <= 0 ) throw new IllegalArgumentException("Upper bound must be greater than lower bound");
|
||||
return lower + nextLong(upper - lower);
|
||||
}
|
||||
/**
|
||||
* Gets a uniform random double in the range [0.0,1.0)
|
||||
* @return a random double at least equal to 0.0 and less than 1.0
|
||||
*/
|
||||
public double nextDouble() {
|
||||
return ( nextLong() & DOUBLE_MASK ) * NORM_53;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a uniform random double in the range [0.0,outer) given a positive parameter outer. If outer
|
||||
* is negative, it will be the (exclusive) lower bound and 0.0 will be the (inclusive) upper bound.
|
||||
* @param outer the exclusive outer bound, can be negative
|
||||
* @return a random double between 0.0 (inclusive) and outer (exclusive)
|
||||
*/
|
||||
public double nextDouble(final double outer) {
|
||||
return nextDouble() * outer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a uniform random float in the range [0.0,1.0)
|
||||
* @return a random float at least equal to 0.0 and less than 1.0
|
||||
*/
|
||||
public float nextFloat() {
|
||||
return (float)( ( nextLong() & FLOAT_MASK ) * NORM_24 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random value, true or false.
|
||||
* Calls nextLong() once.
|
||||
* @return a random true or false value.
|
||||
*/
|
||||
public boolean nextBoolean() {
|
||||
return ( nextLong() & 1 ) != 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a byte array as a parameter, this will fill the array with random bytes (modifying it
|
||||
* in-place). Calls nextLong() {@code Math.ceil(bytes.length / 8.0)} times.
|
||||
* @param bytes a byte array that will have its contents overwritten with random bytes.
|
||||
*/
|
||||
public void nextBytes( final byte[] bytes ) {
|
||||
int i = bytes.length, n = 0;
|
||||
while( i != 0 ) {
|
||||
n = Math.min( i, 8 );
|
||||
for ( long bits = nextLong(); n-- != 0; bits >>= 8 ) bytes[ --i ] = (byte)bits;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the seed of this generator (which is also the current state).
|
||||
* @param seed the seed to use for this LightRNG, as if it was constructed with this seed.
|
||||
*/
|
||||
public void setSeed( final long seed ) {
|
||||
state = seed;
|
||||
}
|
||||
/**
|
||||
* Sets the seed (also the current state) of this generator.
|
||||
* @param seed the seed to use for this LightRNG, as if it was constructed with this seed.
|
||||
*/
|
||||
@Override
|
||||
public void setState( final long seed ) {
|
||||
state = seed;
|
||||
}
|
||||
/**
|
||||
* Gets the current state of this generator.
|
||||
* @return the current seed of this LightRNG, changed once per call to nextLong()
|
||||
*/
|
||||
@Override
|
||||
public long getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances or rolls back the LightRNG's state without actually generating numbers. Skip forward
|
||||
* or backward a number of steps specified by advance, where a step is equal to one call to nextInt().
|
||||
* @param advance Number of future generations to skip past. Can be negative to backtrack.
|
||||
* @return the state after skipping.
|
||||
*/
|
||||
public long skip(long advance)
|
||||
{
|
||||
return state += 0x9E3779B97F4A7C15L * advance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LightRNG (" + state + ")"; // Akarin
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.akarin.api.internal.utils.random;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This is a "fake" LightRandom, backed by the LightRNG.
|
||||
*
|
||||
* This is useful if you want to quickly replace a Random variable with
|
||||
* LightRNG without breaking every code.
|
||||
*/
|
||||
public class LightRandom extends Random {
|
||||
public static LightRNG light = new LightRNG(); // LightRNG, static.
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public int next(int bits) {
|
||||
return light.next(bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
light.nextBytes(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
return light.nextInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt(int n) {
|
||||
return light.nextInt(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
return light.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextBoolean() {
|
||||
return light.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float nextFloat() {
|
||||
return light.nextFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextDouble() {
|
||||
return light.nextDouble();
|
||||
}
|
||||
|
||||
// Akarin start
|
||||
@Override
|
||||
public void setSeed(long seed) {
|
||||
light.setSeed(seed);
|
||||
}
|
||||
// Akarin end
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.akarin.api.internal.utils.random;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This interface defines the interactions required of a random number
|
||||
* generator. It is a replacement for Java's built-in Random because for
|
||||
* improved performance.
|
||||
*
|
||||
* @author Eben Howard - http://squidpony.com - howard@squidpony.com
|
||||
*/
|
||||
public interface RandomnessSource extends Serializable {
|
||||
|
||||
/**
|
||||
* Using this method, any algorithm that might use the built-in Java Random
|
||||
* can interface with this randomness source.
|
||||
*
|
||||
* @param bits the number of bits to be returned
|
||||
* @return the integer containing the appropriate number of bits
|
||||
*/
|
||||
int next(int bits);
|
||||
|
||||
/**
|
||||
*
|
||||
* Using this method, any algorithm that needs to efficiently generate more
|
||||
* than 32 bits of random data can interface with this randomness source.
|
||||
*
|
||||
* Get a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive).
|
||||
* @return a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive)
|
||||
*/
|
||||
long nextLong();
|
||||
|
||||
/**
|
||||
* Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the
|
||||
* copy, both will generate the same sequence of random numbers from the point copy() was called. This just need to
|
||||
* copy the state so it isn't shared, usually, and produce a new value with the same exact state.
|
||||
* @return a copy of this RandomnessSource
|
||||
*/
|
||||
RandomnessSource copy();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.akarin.api.internal.utils.random;
|
||||
|
||||
/**
|
||||
* A simple interface for RandomnessSources that have the additional property of a state that can be re-set.
|
||||
* Created by Tommy Ettinger on 9/15/2015.
|
||||
*/
|
||||
public interface StatefulRandomness extends RandomnessSource {
|
||||
/**
|
||||
* Get the current internal state of the StatefulRandomness as a long.
|
||||
* @return the current internal state of this object.
|
||||
*/
|
||||
long getState();
|
||||
|
||||
/**
|
||||
* Set the current internal state of this StatefulRandomness with a long.
|
||||
*
|
||||
* @param state a 64-bit long. You should avoid passing 0, even though some implementations can handle that.
|
||||
*/
|
||||
void setState(long state);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.akarin.api.internal.utils.thread;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class OpenExecutionException extends ExecutionException {
|
||||
private static final long serialVersionUID = 7830266012832686185L;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* Written by Doug Lea with assistance from members of JCP JSR-166
|
||||
* Expert Group and released to the public domain, as explained at
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
package io.akarin.api.internal.utils.thread;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RunnableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SuspendableExecutorCompletionService<V> implements CompletionService<V> {
|
||||
private final SuspendableThreadPoolExecutor executor;
|
||||
private final BlockingQueue<Future<V>> completionQueue;
|
||||
|
||||
public void suspend() {
|
||||
executor.suspend();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
executor.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* FutureTask extension to enqueue upon completion
|
||||
*/
|
||||
private class QueueingFuture extends FutureTask<Void> {
|
||||
QueueingFuture(RunnableFuture<V> task) {
|
||||
super(task, null);
|
||||
this.task = task;
|
||||
}
|
||||
protected void done() { completionQueue.add(task); }
|
||||
private final Future<V> task;
|
||||
}
|
||||
|
||||
private RunnableFuture<V> newTaskFor(Callable<V> task) {
|
||||
return new FutureTask<V>(task);
|
||||
}
|
||||
|
||||
private RunnableFuture<V> newTaskFor(Runnable task, V result) {
|
||||
return new FutureTask<V>(task, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ExecutorCompletionService using the supplied
|
||||
* executor for base task execution and a
|
||||
* {@link LinkedBlockingQueue} as a completion queue.
|
||||
*
|
||||
* @param executor the executor to use
|
||||
* @throws NullPointerException if executor is {@code null}
|
||||
*/
|
||||
public SuspendableExecutorCompletionService(SuspendableThreadPoolExecutor executor) {
|
||||
if (executor == null)
|
||||
throw new NullPointerException();
|
||||
this.executor = executor;
|
||||
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ExecutorCompletionService using the supplied
|
||||
* executor for base task execution and the supplied queue as its
|
||||
* completion queue.
|
||||
*
|
||||
* @param executor the executor to use
|
||||
* @param completionQueue the queue to use as the completion queue
|
||||
* normally one dedicated for use by this service. This
|
||||
* queue is treated as unbounded -- failed attempted
|
||||
* {@code Queue.add} operations for completed tasks cause
|
||||
* them not to be retrievable.
|
||||
* @throws NullPointerException if executor or completionQueue are {@code null}
|
||||
*/
|
||||
public SuspendableExecutorCompletionService(SuspendableThreadPoolExecutor executor,
|
||||
BlockingQueue<Future<V>> completionQueue) {
|
||||
if (executor == null || completionQueue == null)
|
||||
throw new NullPointerException();
|
||||
this.executor = executor;
|
||||
this.completionQueue = completionQueue;
|
||||
}
|
||||
|
||||
public Future<V> submit(Callable<V> task) {
|
||||
if (task == null) throw new NullPointerException();
|
||||
RunnableFuture<V> f = newTaskFor(task);
|
||||
executor.execute(new QueueingFuture(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
public Future<V> submit(Runnable task, V result) {
|
||||
if (task == null) throw new NullPointerException();
|
||||
RunnableFuture<V> f = newTaskFor(task, result);
|
||||
executor.execute(new QueueingFuture(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
public Future<V> take() throws InterruptedException {
|
||||
return completionQueue.take();
|
||||
}
|
||||
|
||||
public Future<V> poll() {
|
||||
return completionQueue.poll();
|
||||
}
|
||||
|
||||
public Future<V> poll(long timeout, TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
return completionQueue.poll(timeout, unit);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
package io.akarin.api.mixin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
|
||||
public interface IMixinChunk {
|
||||
AtomicInteger getPendingLightUpdates();
|
||||
|
||||
long getLightUpdateTime();
|
||||
|
||||
boolean areNeighborsLoaded();
|
||||
|
||||
@Nullable Chunk getNeighborChunk(int index);
|
||||
|
||||
CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type);
|
||||
|
||||
List<Chunk> getNeighbors();
|
||||
|
||||
void setNeighborChunk(int index, @Nullable Chunk chunk);
|
||||
|
||||
void setLightUpdateTime(long time);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package io.akarin.api.mixin;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
|
||||
public interface IMixinWorldServer {
|
||||
boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk chunk);
|
||||
|
||||
ExecutorService getLightingExecutor();
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package io.akarin.server.core;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -14,7 +16,6 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import io.akarin.api.Akari;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AkarinGlobalConfig {
|
||||
@@ -164,44 +165,19 @@ public class AkarinGlobalConfig {
|
||||
silentAsyncTimings = getBoolean("core.always-silent-async-timing", false);
|
||||
}
|
||||
|
||||
public static boolean legacyWorldTimings;
|
||||
private static void legacyWorldTimings() {
|
||||
legacyWorldTimings = getBoolean("alternative.legacy-world-timings-required", false);
|
||||
}
|
||||
|
||||
public static int timeUpdateInterval;
|
||||
public static long timeUpdateInterval;
|
||||
private static void timeUpdateInterval() {
|
||||
timeUpdateInterval = getSeconds(getString("core.world-time-update-interval", "1s"));
|
||||
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
|
||||
}
|
||||
|
||||
public static int keepAliveSendInterval;
|
||||
public static long keepAliveSendInterval;
|
||||
private static void keepAliveSendInterval() {
|
||||
keepAliveSendInterval = getSeconds(getString("core.keep-alive-packet-send-interval", "15s"));
|
||||
keepAliveSendInterval = getSeconds(getString("core.tick-rate.keep-alive-packet-send-interval", "15s")) * 1000;
|
||||
}
|
||||
|
||||
public static int keepAliveTimeout;
|
||||
public static long keepAliveTimeout;
|
||||
private static void keepAliveTimeout() {
|
||||
keepAliveTimeout = getSeconds(getString("core.keep-alive-response-timeout", "30s"));
|
||||
}
|
||||
|
||||
public static int asyncLightingThreads;
|
||||
private static void asyncLightingThreads() {
|
||||
asyncLightingThreads = getInt("core.async-lighting.executor-threads", 2);
|
||||
}
|
||||
|
||||
public static boolean enableMockPlugin;
|
||||
private static void enableMockPlugin() {
|
||||
enableMockPlugin = getBoolean("core.thread-safe.enable-mock-plugins", false);
|
||||
}
|
||||
|
||||
public static List<String> mockPackageList;
|
||||
private static void mockPluginList() {
|
||||
mockPackageList = getList("core.thread-safe.mock-package-name-contains", Lists.newArrayList("me.konsolas.aac"));
|
||||
}
|
||||
|
||||
public static boolean enableAsyncCatcher;
|
||||
private static void enableAsyncCatcher() {
|
||||
enableAsyncCatcher = getBoolean("core.thread-safe.async-catcher.enable", false);
|
||||
keepAliveTimeout = getSeconds(getString("core.keep-alive-response-timeout", "30s")) * 1000;
|
||||
}
|
||||
|
||||
public static boolean throwOnAsyncCaught;
|
||||
@@ -209,13 +185,81 @@ public class AkarinGlobalConfig {
|
||||
throwOnAsyncCaught = getBoolean("core.thread-safe.async-catcher.throw-on-caught", true);
|
||||
}
|
||||
|
||||
public static boolean asyncLightingWorkStealing;
|
||||
private static void asyncLightingWorkStealing() {
|
||||
asyncLightingWorkStealing = getBoolean("core.async-lighting.use-work-stealing", false);
|
||||
}
|
||||
|
||||
public static boolean allowSpawnerModify;
|
||||
private static void allowSpawnerModify() {
|
||||
allowSpawnerModify = getBoolean("alternative.allow-spawner-modify", true);
|
||||
}
|
||||
|
||||
public static boolean noResponseDoGC;
|
||||
private static void noResponseDoGC() {
|
||||
noResponseDoGC = getBoolean("alternative.gc-before-stuck-restart", true);
|
||||
}
|
||||
|
||||
public static String messageKick;
|
||||
public static String messageBan;
|
||||
public static String messageBanReason;
|
||||
public static String messageBanExpires;
|
||||
public static String messageBanIp;
|
||||
public static String messageDupLogin;
|
||||
public static String messageJoin;
|
||||
public static String messageJoinRenamed;
|
||||
public static String messageKickKeepAlive;
|
||||
public static String messagePlayerQuit;
|
||||
private static void messagekickKeepAlive() {
|
||||
messageKick = getString("messages.disconnect.kick-player", "Kicked by an operator.");
|
||||
messageBan = getString("messages.disconnect.ban-player-name", "You are banned from this server! %s %s");
|
||||
messageBanReason = getString("messages.disconnect.ban-reason", "\nReason: ");
|
||||
messageBanExpires = getString("messages.disconnect.ban-expires", "\nYour ban will be removed on ");
|
||||
messageBanIp = getString("messages.disconnect.ban-player-ip", "Your IP address is banned from this server! %s %s");
|
||||
messageDupLogin = getString("messages.disconnect.kick-player-duplicate-login", "You logged in from another location");
|
||||
messageJoin = getString("messages.connect.player-join-server", "§e%s joined the game");
|
||||
messageJoinRenamed = getString("messages.connect.renamed-player-join-server", "§e%s (formerly known as %s) joined the game");
|
||||
messageKickKeepAlive = getString("messages.disconnect.kick-player-timeout-keep-alive", "Timed out");
|
||||
messagePlayerQuit = getString("messages.disconnect.player-quit-server", "§e%s left the game");
|
||||
}
|
||||
|
||||
public static String serverBrandName;
|
||||
private static void serverBrandName() {
|
||||
serverBrandName = getString("alternative.modified-server-brand-name", "");
|
||||
}
|
||||
|
||||
public static boolean disableEndPortalCreate;
|
||||
private static void disableEndPortalCreate() {
|
||||
disableEndPortalCreate = getBoolean("alternative.disable-end-portal-create", false);
|
||||
}
|
||||
|
||||
public static int primaryThreadPriority;
|
||||
private static void primaryThreadPriority() {
|
||||
primaryThreadPriority = getInt("core.primary-thread-priority", 7);
|
||||
}
|
||||
|
||||
public static long playersInfoUpdateInterval;
|
||||
private static void playersInfoUpdateInterval() {
|
||||
playersInfoUpdateInterval = getSeconds(getString("core.tick-rate.players-info-update-interval", "30s")) * 10;
|
||||
}
|
||||
|
||||
public static long versionUpdateInterval;
|
||||
private static void versionUpdateInterval() {
|
||||
versionUpdateInterval = getSeconds(getString("alternative.version-update-interval", "3600s")) * 1000; // 1 hour
|
||||
}
|
||||
|
||||
public static boolean sendLightOnlyChunkSection;
|
||||
private static void sendLightOnlyChunkSection() {
|
||||
sendLightOnlyChunkSection = getBoolean("core.send-light-only-chunk-sections", true);
|
||||
}
|
||||
|
||||
public static boolean forceHardcoreDifficulty;
|
||||
private static void forceHardcoreDifficulty() {
|
||||
forceHardcoreDifficulty = getBoolean("alternative.force-difficulty-on-hardcore", true);
|
||||
}
|
||||
|
||||
public static int fileIOThreads;
|
||||
private static void fileIOThreads() {
|
||||
fileIOThreads = getInt("core.chunk-save-threads", 2);
|
||||
}
|
||||
|
||||
public static int parallelMode;
|
||||
private static void parallelMode() {
|
||||
parallelMode = getInt("core.parallel-mode", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
package io.akarin.server.core;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.EnumDifficulty;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.PacketPlayOutKeepAlive;
|
||||
import net.minecraft.server.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.PacketPlayOutUpdateTime;
|
||||
import net.minecraft.server.PlayerConnection;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
public class AkarinSlackScheduler extends Thread {
|
||||
public static AkarinSlackScheduler get() {
|
||||
return Singleton.instance;
|
||||
}
|
||||
|
||||
public static void boot() {
|
||||
Singleton.instance.setName("Akarin Slack Scheduler Thread");
|
||||
Singleton.instance.setPriority(MIN_PRIORITY);
|
||||
Singleton.instance.setDaemon(true);
|
||||
Singleton.instance.start();
|
||||
public void boot() {
|
||||
setName("Akarin Slack Scheduler Thread");
|
||||
setDaemon(true);
|
||||
start();
|
||||
Akari.logger.info("Slack scheduler service started");
|
||||
}
|
||||
|
||||
@@ -24,49 +29,80 @@ public class AkarinSlackScheduler extends Thread {
|
||||
private static final AkarinSlackScheduler instance = new AkarinSlackScheduler();
|
||||
}
|
||||
|
||||
/*
|
||||
* Timers
|
||||
*/
|
||||
private long updateTime;
|
||||
private long resendPlayersInfo;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
|
||||
// Send time updates to everyone, it will get the right time from the world the player is in.
|
||||
if (++updateTime == AkarinGlobalConfig.timeUpdateInterval * 10) {
|
||||
while (server.isRunning()) {
|
||||
long startProcessTiming = System.currentTimeMillis();
|
||||
// Send time updates to everyone, it will get the right time from the world the player is in.
|
||||
// Time update, from MinecraftServer#D
|
||||
if (++updateTime >= AkarinGlobalConfig.timeUpdateInterval) {
|
||||
for (EntityPlayer player : server.getPlayerList().players) {
|
||||
// Add support for per player time
|
||||
player.playerConnection.sendPacket(new PacketPlayOutUpdateTime(player.world.getTime(), player.getPlayerTime(), player.world.getGameRules().getBoolean("doDaylightCycle")));
|
||||
}
|
||||
updateTime = 0;
|
||||
}
|
||||
|
||||
// Keep alive, from PlayerConnection#e
|
||||
for (EntityPlayer player : server.getPlayerList().players) {
|
||||
player.playerConnection.sendPacket(new PacketPlayOutUpdateTime(player.world.getTime(), player.getPlayerTime(), player.world.getGameRules().getBoolean("doDaylightCycle"))); // Add support for per player time
|
||||
}
|
||||
updateTime = 0;
|
||||
}
|
||||
|
||||
for (EntityPlayer player : server.getPlayerList().players) {
|
||||
PlayerConnection conn = player.playerConnection;
|
||||
// Paper - give clients a longer time to respond to pings as per pre 1.12.2 timings
|
||||
// This should effectively place the keepalive handling back to "as it was" before 1.12.2
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long elapsedTime = currentTime - conn.getLastPing();
|
||||
if (conn.isPendingPing()) {
|
||||
// We're pending a ping from the client
|
||||
if (!conn.processedDisconnect && elapsedTime >= AkarinGlobalConfig.keepAliveTimeout * 1000L) { // check keepalive limit, don't fire if already disconnected
|
||||
Akari.callbackQueue.add(() -> {
|
||||
Akari.logger.warn("{} was kicked due to keepalive timeout!", conn.player.getName()); // more info
|
||||
conn.disconnect("disconnect.timeout");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (elapsedTime >= AkarinGlobalConfig.keepAliveSendInterval * 1000L) { // 15 seconds default
|
||||
conn.setPendingPing(true);
|
||||
conn.setLastPing(currentTime);
|
||||
conn.setKeepAliveID(currentTime);
|
||||
conn.sendPacket(new PacketPlayOutKeepAlive(conn.getKeepAliveID()));
|
||||
PlayerConnection conn = player.playerConnection;
|
||||
// Paper - give clients a longer time to respond to pings as per pre 1.12.2 timings
|
||||
// This should effectively place the keepalive handling back to "as it was" before 1.12.2
|
||||
long currentTime = System.nanoTime() / 1000000L;
|
||||
long elapsedTime = currentTime - conn.getLastPing();
|
||||
if (conn.isPendingPing()) {
|
||||
// We're pending a ping from the client
|
||||
if (!conn.processedDisconnect && elapsedTime >= AkarinGlobalConfig.keepAliveTimeout) { // check keepalive limit, don't fire if already disconnected
|
||||
Akari.callbackQueue.add(() -> {
|
||||
Akari.logger.warn("{} was kicked due to keepalive timeout!", conn.player.getName()); // more info
|
||||
conn.disconnect("disconnect.timeout");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (elapsedTime >= AkarinGlobalConfig.keepAliveSendInterval) { // 15 seconds default
|
||||
conn.setPendingPing(true);
|
||||
conn.setLastPing(currentTime);
|
||||
conn.setKeepAliveID(currentTime);
|
||||
conn.sendPacket(new PacketPlayOutKeepAlive(conn.getKeepAliveID())); // 15s lagg you should stop your server
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Akari.logger.warn("Slack scheduler thread was interrupted unexpectly!");
|
||||
ex.printStackTrace();
|
||||
|
||||
// Force hardcore difficulty, from WorldServer#doTick
|
||||
if (AkarinGlobalConfig.forceHardcoreDifficulty)
|
||||
for (WorldServer world : server.worlds) {
|
||||
if (world.getWorldData().isHardcore() && world.getDifficulty() != EnumDifficulty.HARD) {
|
||||
world.getWorldData().setDifficulty(EnumDifficulty.HARD);
|
||||
}
|
||||
}
|
||||
|
||||
// Update player info, from PlayerList#tick
|
||||
if (++resendPlayersInfo > AkarinGlobalConfig.playersInfoUpdateInterval) {
|
||||
for (EntityPlayer player : server.getPlayerList().players) {
|
||||
player.playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, Iterables.filter(server.getPlayerList().players, new Predicate<EntityPlayer>() {
|
||||
@Override
|
||||
public boolean apply(EntityPlayer each) {
|
||||
return player.getBukkitEntity().canSee(each.getBukkitEntity());
|
||||
}
|
||||
})));
|
||||
}
|
||||
resendPlayersInfo = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
long sleepFixed = 100 - (System.currentTimeMillis() - startProcessTiming);
|
||||
if (sleepFixed > 0) Thread.sleep(sleepFixed);
|
||||
} catch (InterruptedException interrupted) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package io.akarin.server.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelException;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import net.minecraft.server.EnumProtocolDirection;
|
||||
import net.minecraft.server.HandshakeListener;
|
||||
import net.minecraft.server.LegacyPingHandler;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.NetworkManager;
|
||||
import net.minecraft.server.PacketDecoder;
|
||||
import net.minecraft.server.PacketEncoder;
|
||||
import net.minecraft.server.PacketPrepender;
|
||||
import net.minecraft.server.PacketSplitter;
|
||||
|
||||
public class ChannelAdapter extends ChannelInitializer<Channel> {
|
||||
private final List<NetworkManager> managers;
|
||||
|
||||
public ChannelAdapter(List<NetworkManager> list) {
|
||||
managers = list;
|
||||
}
|
||||
|
||||
public static ChannelAdapter create(List<NetworkManager> managers) {
|
||||
return new ChannelAdapter(managers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel channel) {
|
||||
try {
|
||||
channel.config().setOption(ChannelOption.TCP_NODELAY, true);
|
||||
} catch (ChannelException ex) {
|
||||
;
|
||||
}
|
||||
channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30))
|
||||
.addLast("legacy_query", new LegacyPingHandler(MinecraftServer.getServer().getServerConnection()))
|
||||
.addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND))
|
||||
.addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND));
|
||||
|
||||
NetworkManager manager = new NetworkManager(EnumProtocolDirection.SERVERBOUND);
|
||||
managers.add(manager);
|
||||
|
||||
channel.pipeline().addLast("packet_handler", manager);
|
||||
manager.setPacketListener(new HandshakeListener(MinecraftServer.getServer(), manager));
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
@Mixin(value = Main.class, remap = false)
|
||||
public class Bootstrap {
|
||||
public abstract class Bootstrap {
|
||||
@Inject(method = "main([Ljava/lang/String;)V", at = @At("HEAD"))
|
||||
private static void premain(CallbackInfo info) {
|
||||
AkarinGlobalConfig.init(new File("akarin.yml"));
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||
import net.minecraft.server.EULA;
|
||||
|
||||
@Mixin(value = EULA.class, remap = false)
|
||||
public class DummyEula {
|
||||
public abstract class DummyEula {
|
||||
/**
|
||||
* Read then check the EULA file <i>formerly</i>
|
||||
* @param file
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.destroystokyo.paper.Metrics;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(targets = "com.destroystokyo.paper.Metrics$PaperMetrics", remap = false)
|
||||
public class MetricsBootstrap {
|
||||
public abstract class MetricsBootstrap {
|
||||
@Overwrite
|
||||
static void startMetrics() {
|
||||
// Get the config file
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.destroystokyo.paper.Metrics;
|
||||
import com.destroystokyo.paper.Metrics.CustomChart;
|
||||
|
||||
@Mixin(value = Metrics.class, remap = false)
|
||||
public class MixinMetrics {
|
||||
public abstract class MixinMetrics {
|
||||
// The url to which the data is sent - bukkit/Torch (keep our old name)
|
||||
private final static String URL = "https://bStats.org/submitData/bukkit";
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.akarin.server.mixin.bootstrap;
|
||||
|
||||
import org.spigotmc.RestartCommand;
|
||||
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 io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
@Mixin(value = RestartCommand.class, remap = false)
|
||||
public abstract class MixinRestartCommand {
|
||||
@Inject(method = "restart()V", at = @At("HEAD"))
|
||||
private static void beforeRestart(CallbackInfo ci) {
|
||||
if (AkarinGlobalConfig.noResponseDoGC) {
|
||||
Akari.logger.warn("Attempting to garbage collect, may takes a few seconds");
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import net.minecraft.server.BiomeBase;
|
||||
import net.minecraft.server.Block;
|
||||
import net.minecraft.server.BlockFire;
|
||||
@@ -24,7 +24,7 @@ import net.minecraft.server.PotionRegistry;
|
||||
import net.minecraft.server.SoundEffect;
|
||||
|
||||
@Mixin(value = DispenserRegistry.class, remap = false)
|
||||
public class ParallelRegistry {
|
||||
public abstract class ParallelRegistry {
|
||||
/**
|
||||
* Registry order: SoundEffect -> Block
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.spigotmc.RestartCommand;
|
||||
import org.spigotmc.WatchdogThread;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -20,9 +19,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = WatchdogThread.class, remap = false)
|
||||
public class Watchcat extends Thread {
|
||||
public abstract class Watchcat extends Thread {
|
||||
@Shadow private static WatchdogThread instance;
|
||||
@Shadow private @Final long timeoutTime;
|
||||
@Shadow private @Final long earlyWarningEvery; // Paper - Timeout time for just printing a dump but not restarting
|
||||
@Shadow private @Final long earlyWarningDelay; // Paper
|
||||
@Shadow public static volatile boolean hasStarted; // Paper
|
||||
@Shadow private long lastEarlyWarning; // Paper - Keep track of short dump times to avoid spamming console with short dumps
|
||||
@Shadow private @Final boolean restart;
|
||||
@Shadow private volatile long lastTick;
|
||||
@Shadow private volatile boolean stopping;
|
||||
@@ -38,48 +41,73 @@ public class Watchcat extends Thread {
|
||||
@Overwrite
|
||||
public void run() {
|
||||
while (!stopping) {
|
||||
//
|
||||
if (lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime && !Boolean.getBoolean("disable.watchdog")) { // Paper - Add property to disable
|
||||
Logger log = Bukkit.getServer().getLogger();
|
||||
log.log(Level.SEVERE, "Server has stopped responding!");
|
||||
log.log(Level.SEVERE, "Please report this to https://github.com/Akarin-project/Akarin/issues");
|
||||
log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
|
||||
log.log(Level.SEVERE, "Akarin version: " + Bukkit.getServer().getVersion());
|
||||
//
|
||||
if (net.minecraft.server.World.haveWeSilencedAPhysicsCrash) {
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
log.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
|
||||
log.log(Level.SEVERE, "near " + net.minecraft.server.World.blockLocation);
|
||||
}
|
||||
// Paper start - Warn in watchdog if an excessive velocity was ever set
|
||||
if (CraftServer.excessiveVelEx != null) {
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
|
||||
log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
|
||||
log.log(Level.SEVERE, CraftServer.excessiveVelEx.getMessage());
|
||||
for (StackTraceElement stack : CraftServer.excessiveVelEx.getStackTrace()) {
|
||||
log.log(Level.SEVERE, "\t\t" + stack);
|
||||
}
|
||||
}
|
||||
// Paper start
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if ( lastTick != 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") )
|
||||
{
|
||||
boolean isLongTimeout = currentTime > lastTick + timeoutTime;
|
||||
// Don't spam early warning dumps
|
||||
if (!isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay))
|
||||
continue;
|
||||
lastEarlyWarning = currentTime;
|
||||
// Paper end
|
||||
Logger log = Bukkit.getServer().getLogger();
|
||||
// Paper start - Different message when it's a short timeout
|
||||
if (isLongTimeout) {
|
||||
log.log(Level.SEVERE, "The server has stopped responding!");
|
||||
log.log(Level.SEVERE, "Please report this to https://github.com/Akarin-project/Akarin/issues"); // Akarin
|
||||
log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
|
||||
log.log(Level.SEVERE, "Akarin version: " + Bukkit.getServer().getVersion()); // Akarin
|
||||
//
|
||||
if (net.minecraft.server.World.haveWeSilencedAPhysicsCrash) {
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
log.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
|
||||
log.log(Level.SEVERE, "near " + net.minecraft.server.World.blockLocation);
|
||||
}
|
||||
// Paper start - Warn in watchdog if an excessive velocity was ever set
|
||||
if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) {
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
|
||||
log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
|
||||
log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage());
|
||||
for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) {
|
||||
log.log(Level.SEVERE, "\t\t" + stack);
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
} else {
|
||||
// log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); // Akarin
|
||||
log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
|
||||
}
|
||||
// Paper end - Different message for short timeout
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
log.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Akarin!):");
|
||||
dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE), log);
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
//
|
||||
log.log(Level.SEVERE, "Entire Thread Dump:");
|
||||
ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
|
||||
for (ThreadInfo thread : threads) {
|
||||
dumpThread(thread, log);
|
||||
// Paper start - Only print full dump on long timeouts
|
||||
if (isLongTimeout) {
|
||||
log.log(Level.SEVERE, "Entire Thread Dump:");
|
||||
ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
|
||||
for (ThreadInfo thread : threads) {
|
||||
dumpThread(thread, log);
|
||||
}
|
||||
} else {
|
||||
// log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); // Akarin
|
||||
}
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
|
||||
if (restart) RestartCommand.restart();
|
||||
break;
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
|
||||
if ( isLongTimeout )
|
||||
{
|
||||
if (restart) {
|
||||
RestartCommand.restart();
|
||||
}
|
||||
break;
|
||||
} // Paper end
|
||||
}
|
||||
|
||||
try {
|
||||
sleep(9000); // Akarin
|
||||
sleep(1000); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout
|
||||
} catch (InterruptedException ex) {
|
||||
interrupt();
|
||||
}
|
||||
|
||||
@@ -5,17 +5,18 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = AsyncCatcher.class, remap = false)
|
||||
public class MixinAsyncCatcher {
|
||||
public abstract class MixinAsyncCatcher {
|
||||
@Shadow public static boolean enabled;
|
||||
|
||||
@Overwrite
|
||||
public static void catchOp(String reason) {
|
||||
if (AkarinGlobalConfig.enableAsyncCatcher && enabled && Thread.currentThread() != MinecraftServer.getServer().primaryThread) {
|
||||
if (enabled) {
|
||||
if (Akari.isPrimaryThread()) return;
|
||||
|
||||
if (AkarinGlobalConfig.throwOnAsyncCaught) {
|
||||
throw new IllegalStateException("Asynchronous " + reason + "!");
|
||||
} else {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.Chunk;
|
||||
|
||||
@Mixin(value = ChunkIOExecutor.class, remap = false)
|
||||
public class MixinChunkIOExecutor {
|
||||
public abstract class MixinChunkIOExecutor {
|
||||
@Shadow @Final static int BASE_THREADS;
|
||||
@Shadow @Mutable @Final static int PLAYERS_PER_THREAD;
|
||||
@Shadow @Final private static AsynchronousExecutor<?, Chunk, Runnable, RuntimeException> instance;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.ChunkSection;
|
||||
|
||||
@Mixin(value = ChunkSection.class, remap = false)
|
||||
public abstract class MixinChunkSection {
|
||||
@Shadow private int nonEmptyBlockCount;
|
||||
|
||||
@Overwrite // OBFHELPER: isEmpty
|
||||
public boolean a() {
|
||||
return AkarinGlobalConfig.sendLightOnlyChunkSection ? false : nonEmptyBlockCount == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.CommandAbstract;
|
||||
import net.minecraft.server.CommandBan;
|
||||
import net.minecraft.server.CommandException;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.ExceptionUsage;
|
||||
import net.minecraft.server.GameProfileBanEntry;
|
||||
import net.minecraft.server.ICommand;
|
||||
import net.minecraft.server.ICommandListener;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = CommandBan.class, remap = false)
|
||||
public abstract class MixinCommandBan {
|
||||
@Overwrite
|
||||
public void execute(MinecraftServer server, ICommandListener sender, String[] args) throws CommandException {
|
||||
if (args.length >= 1 && args[0].length() > 1) {
|
||||
GameProfile profile = server.getUserCache().getProfile(args[0]);
|
||||
|
||||
if (profile == null) {
|
||||
throw new CommandException("commands.ban.failed", new Object[] {args[0]});
|
||||
} else {
|
||||
// Akarin start - use string
|
||||
boolean hasReason = true; // Akarin
|
||||
String message = null;
|
||||
if (args.length >= 2) {
|
||||
message = "";
|
||||
for (int i = 2; i < args.length; i++) {
|
||||
message = message + args[i];
|
||||
}
|
||||
} else {
|
||||
hasReason = false; // Akarin
|
||||
message = Akari.EMPTY_STRING; // Akarin - modify message
|
||||
}
|
||||
// Akarin end
|
||||
|
||||
GameProfileBanEntry entry = new GameProfileBanEntry(profile, (Date) null, sender.getName(), (Date) null, message);
|
||||
|
||||
server.getPlayerList().getProfileBans().add(entry);
|
||||
EntityPlayer entityplayer = server.getPlayerList().getPlayer(args[0]);
|
||||
|
||||
if (entityplayer != null) {
|
||||
entityplayer.playerConnection.disconnect(hasReason ? message : AkarinGlobalConfig.messageBan);
|
||||
}
|
||||
|
||||
CommandAbstract.a(sender, (ICommand) this, "commands.ban.success", args[0]); // OBFHELPER: notifyCommandListener
|
||||
}
|
||||
} else {
|
||||
throw new ExceptionUsage("commands.ban.usage");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.CommandAbstract;
|
||||
import net.minecraft.server.CommandBanIp;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.ICommand;
|
||||
import net.minecraft.server.ICommandListener;
|
||||
import net.minecraft.server.IpBanEntry;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = CommandBanIp.class, remap = false)
|
||||
public abstract class MixinCommandBanIp {
|
||||
@Overwrite // OBFHELPER: banIp
|
||||
protected void a(MinecraftServer server, ICommandListener sender, String args, @Nullable String banReason) {
|
||||
// Akarin start - modify message
|
||||
boolean hasReason = true;
|
||||
if (banReason == null) {
|
||||
banReason = Akari.EMPTY_STRING;
|
||||
hasReason = false;
|
||||
}
|
||||
// Akarin end
|
||||
IpBanEntry ipbanentry = new IpBanEntry(args, (Date) null, sender.getName(), (Date) null, banReason);
|
||||
|
||||
server.getPlayerList().getIPBans().add(ipbanentry);
|
||||
List<EntityPlayer> withIpPlayers = server.getPlayerList().b(args); // OBFHELPER: getPlayersMatchingAddress
|
||||
String[] banPlayerNames = new String[withIpPlayers.size()];
|
||||
|
||||
for (int i = 0; i < banPlayerNames.length; i++) {
|
||||
EntityPlayer each = withIpPlayers.get(i);
|
||||
banPlayerNames[i] = each.getName();
|
||||
each.playerConnection.disconnect(hasReason ? banReason : AkarinGlobalConfig.messageBanIp); // Akarin
|
||||
}
|
||||
|
||||
if (withIpPlayers.isEmpty()) {
|
||||
CommandAbstract.a(sender, (ICommand) this, "commands.banip.success", args); // OBFHELPER: notifyCommandListener
|
||||
} else {
|
||||
CommandAbstract.a(sender, (ICommand) this, "commands.banip.success.players", args, CommandAbstract.a(banPlayerNames)); // OBFHELPER: notifyCommandListener - joinNiceString
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.CommandAbstract;
|
||||
import net.minecraft.server.CommandException;
|
||||
import net.minecraft.server.CommandKick;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.ExceptionPlayerNotFound;
|
||||
import net.minecraft.server.ExceptionUsage;
|
||||
import net.minecraft.server.ICommand;
|
||||
import net.minecraft.server.ICommandListener;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = CommandKick.class, remap = false)
|
||||
public abstract class MixinCommandKick {
|
||||
@Overwrite
|
||||
public void execute(MinecraftServer server, ICommandListener sender, String[] args) throws CommandException {
|
||||
if (args.length > 0 && args[0].length() > 1) {
|
||||
EntityPlayer target = server.getPlayerList().getPlayer(args[0]);
|
||||
|
||||
if (target == null) {
|
||||
throw new ExceptionPlayerNotFound("commands.generic.player.notFound", args[0]);
|
||||
} else {
|
||||
if (args.length >= 2) {
|
||||
// Akarin start - use string
|
||||
String message = "";
|
||||
for (int i = 2; i < args.length; i++) {
|
||||
message = message + args[i];
|
||||
}
|
||||
target.playerConnection.disconnect(message);
|
||||
CommandAbstract.a(sender, (ICommand) this, "commands.kick.success.reason", target.getName(), message); // OBFHELPER: notifyCommandListener
|
||||
// Akarin end
|
||||
} else {
|
||||
target.playerConnection.disconnect(AkarinGlobalConfig.messageKick); // Akarin
|
||||
CommandAbstract.a(sender, (ICommand) this, "commands.kick.success", target.getName()); // OBFHELPER: notifyCommandListener
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new ExceptionUsage("commands.kick.usage");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,38 +7,40 @@ import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import me.nallar.whocalled.WhoCalled;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = CraftServer.class, remap = false)
|
||||
public class MixinCraftServer {
|
||||
public abstract class MixinCraftServer {
|
||||
@Shadow @Final @Mutable private String serverName;
|
||||
@Shadow @Final @Mutable private String serverVersion;
|
||||
@Shadow @Final protected MinecraftServer console;
|
||||
private boolean needApplyServerName = true;
|
||||
private boolean needApplyServerVersion = true;
|
||||
|
||||
@Overwrite
|
||||
public String getName() {
|
||||
// We cannot apply the name modification in <init> method,
|
||||
// cause the initializer will be added to the tail
|
||||
if (needApplyServerName) {
|
||||
serverName = "Akarin";
|
||||
serverName = AkarinGlobalConfig.serverBrandName.equals(Akari.EMPTY_STRING) ? "Akarin" : AkarinGlobalConfig.serverBrandName;
|
||||
needApplyServerName = false;
|
||||
}
|
||||
return serverName;
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public boolean isPrimaryThread() {
|
||||
if (AkarinGlobalConfig.enableMockPlugin) {
|
||||
// Mock forcely main thread plugins
|
||||
String callerPackage = WhoCalled.$.getCallingClass().getPackage().getName();
|
||||
if (callerPackage.startsWith("net.minecraft") || callerPackage.startsWith("org.bukkit") ||
|
||||
callerPackage.startsWith("co.aikar") || callerPackage.startsWith("io.akarin")) return Thread.currentThread().equals(console.primaryThread);
|
||||
for (String contains : AkarinGlobalConfig.mockPackageList) {
|
||||
if (callerPackage.contains(contains)) return true;
|
||||
}
|
||||
public String getVersion() {
|
||||
if (needApplyServerVersion) {
|
||||
serverVersion = AkarinGlobalConfig.serverBrandName.equals(Akari.EMPTY_STRING) ? serverVersion : serverVersion.replace("Akarin", AkarinGlobalConfig.serverBrandName);
|
||||
needApplyServerVersion = false;
|
||||
}
|
||||
return Thread.currentThread().equals(console.primaryThread);
|
||||
return serverVersion + " (MC: " + console.getVersion() + ")";
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public boolean isPrimaryThread() {
|
||||
return Akari.isPrimaryThread();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.destroystokyo.paper.PaperConfig;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.FileIOThread;
|
||||
import net.minecraft.server.IAsyncChunkSaver;
|
||||
|
||||
@Mixin(value = FileIOThread.class, remap = false)
|
||||
public abstract class MixinFileIOThread {
|
||||
private final Executor executor = Executors.newFixedThreadPool(AkarinGlobalConfig.fileIOThreads, new ThreadFactoryBuilder().setNameFormat("Akarin File IO Thread - %1$d").setPriority(1).build());
|
||||
private final AtomicInteger queuedChunkCounter = new AtomicInteger(0);
|
||||
|
||||
@Shadow(aliases = "e") private volatile boolean isAwaitFinish;
|
||||
|
||||
@Overwrite // OBFHELPER: saveChunk
|
||||
public void a(IAsyncChunkSaver iasyncchunksaver) {
|
||||
queuedChunkCounter.incrementAndGet();
|
||||
executor.execute(() -> writeChunk(iasyncchunksaver));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a chunk, re-add to the queue if unsuccessful
|
||||
*/
|
||||
private void writeChunk(IAsyncChunkSaver iasyncchunksaver) {
|
||||
if (!iasyncchunksaver.a()) { // PAIL: WriteNextIO() -> Returns if the write was unsuccessful
|
||||
queuedChunkCounter.decrementAndGet();
|
||||
|
||||
if (PaperConfig.enableFileIOThreadSleep) {
|
||||
try {
|
||||
Thread.sleep(isAwaitFinish ? 0L : 2L);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeChunk(iasyncchunksaver);
|
||||
}
|
||||
}
|
||||
|
||||
@Overwrite // OBFHELPER: waitForFinish
|
||||
public void b() throws InterruptedException {
|
||||
isAwaitFinish = true;
|
||||
while (queuedChunkCounter.get() != 0) Thread.sleep(9L);
|
||||
isAwaitFinish = false;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,38 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.bukkit.craftbukkit.util.Waitable;
|
||||
import org.spigotmc.AsyncCatcher;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = MCUtil.class, remap = false)
|
||||
public class MixinMCUtil {
|
||||
public abstract class MixinMCUtil {
|
||||
@Overwrite
|
||||
public static <T> T ensureMain(String reason, Supplier<T> run) {
|
||||
if (AsyncCatcher.enabled && !Akari.isPrimaryThread()) {
|
||||
new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace();
|
||||
Waitable<T> wait = new Waitable<T>() {
|
||||
@Override
|
||||
protected T evaluate() {
|
||||
return run.get();
|
||||
}
|
||||
};
|
||||
MinecraftServer.getServer().processQueue.add(wait);
|
||||
try {
|
||||
return wait.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return run.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,18 @@ package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
@@ -16,9 +24,12 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import co.aikar.timings.MinecraftTimings;
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import io.akarin.server.core.AkarinSlackScheduler;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.CrashReport;
|
||||
import net.minecraft.server.CustomFunctionData;
|
||||
import net.minecraft.server.ITickable;
|
||||
@@ -33,11 +44,37 @@ import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = MinecraftServer.class, remap = false)
|
||||
public abstract class MixinMinecraftServer {
|
||||
@Shadow @Final public Thread primaryThread;
|
||||
private boolean tickedPrimaryEntities;
|
||||
private int cachedWorldSize;
|
||||
|
||||
@Overwrite
|
||||
public String getServerModName() {
|
||||
return "Akarin";
|
||||
}
|
||||
|
||||
@Inject(method = "run()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/MinecraftServer.aw()J",
|
||||
shift = At.Shift.BEFORE
|
||||
))
|
||||
private void prerun(CallbackInfo info) {
|
||||
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
||||
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
||||
Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
//TileEntityHopper.skipHopperEvents = world.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0;
|
||||
}
|
||||
AkarinSlackScheduler.get().boot();
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public boolean isMainThread() {
|
||||
return Akari.isPrimaryThread();
|
||||
}
|
||||
|
||||
/*
|
||||
* Forcely disable snooper
|
||||
*/
|
||||
@@ -47,34 +84,81 @@ public abstract class MixinMinecraftServer {
|
||||
@Overwrite
|
||||
public void b(MojangStatisticsGenerator generator) {}
|
||||
|
||||
@Inject(method = "run()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/MinecraftServer.aw()J",
|
||||
shift = At.Shift.BEFORE
|
||||
))
|
||||
private void prerun(CallbackInfo info) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
TileEntityHopper.skipHopperEvents = world.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0;
|
||||
/*
|
||||
* Parallel spawn chunks generation
|
||||
*/
|
||||
@Shadow public abstract boolean isRunning();
|
||||
@Shadow(aliases = "a_") protected abstract void output(String s, int i);
|
||||
@Shadow(aliases = "t") protected abstract void enablePluginsPostWorld();
|
||||
|
||||
private void prepareChunks(WorldServer world, int index) {
|
||||
MinecraftServer.LOGGER.info("Preparing start region for level " + index + " (Seed: " + world.getSeed() + ")");
|
||||
BlockPosition spawnPos = world.getSpawn();
|
||||
long lastRecord = System.currentTimeMillis();
|
||||
|
||||
int preparedChunks = 0;
|
||||
short radius = world.paperConfig.keepLoadedRange;
|
||||
for (int skipX = -radius; skipX <= radius && isRunning(); skipX += 16) {
|
||||
for (int skipZ = -radius; skipZ <= radius && isRunning(); skipZ += 16) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (now - lastRecord > 1000L) {
|
||||
output("Preparing spawn area (level " + index + ") ", preparedChunks * 100 / 625);
|
||||
lastRecord = now;
|
||||
}
|
||||
|
||||
preparedChunks++;
|
||||
world.getChunkProviderServer().getChunkAt(spawnPos.getX() + skipX >> 4, spawnPos.getZ() + skipZ >> 4);
|
||||
}
|
||||
}
|
||||
AkarinSlackScheduler.boot();
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
protected void l() throws InterruptedException {
|
||||
ExecutorCompletionService<?> executor = new ExecutorCompletionService<>(Executors.newFixedThreadPool(worlds.size(), new AssignableFactory("Akarin Parallel Terrain Generation Thread - $")));
|
||||
|
||||
for (int index = 0; index < worlds.size(); index++) {
|
||||
WorldServer world = this.worlds.get(index);
|
||||
if (!world.getWorld().getKeepSpawnInMemory()) continue;
|
||||
|
||||
int fIndex = index;
|
||||
executor.submit(() -> prepareChunks(world, fIndex), null);
|
||||
}
|
||||
|
||||
for (WorldServer world : this.worlds) {
|
||||
if (world.getWorld().getKeepSpawnInMemory()) executor.take();
|
||||
}
|
||||
if (WorldLoadEvent.getHandlerList().getRegisteredListeners().length != 0) {
|
||||
for (WorldServer world : this.worlds) {
|
||||
this.server.getPluginManager().callEvent(new WorldLoadEvent(world.getWorld()));
|
||||
}
|
||||
}
|
||||
|
||||
enablePluginsPostWorld();
|
||||
}
|
||||
|
||||
/*
|
||||
* Parallel world ticking
|
||||
*/
|
||||
@Shadow public CraftServer server;
|
||||
@Shadow @Mutable protected Queue<FutureTask<?>> j;
|
||||
@Shadow public Queue<Runnable> processQueue;
|
||||
@Shadow private int ticks;
|
||||
@Shadow public List<WorldServer> worlds;
|
||||
@Shadow private PlayerList v;
|
||||
@Shadow @Final private List<ITickable> o;
|
||||
@Shadow(aliases = "v") private PlayerList playerList;
|
||||
@Shadow(aliases = "o") @Final private List<ITickable> tickables;
|
||||
|
||||
@Shadow public abstract PlayerList getPlayerList();
|
||||
@Shadow public abstract ServerConnection an();
|
||||
@Shadow public abstract CustomFunctionData aL();
|
||||
@Shadow(aliases = "an") public abstract ServerConnection serverConnection();
|
||||
@Shadow(aliases = "aL") public abstract CustomFunctionData functionManager();
|
||||
|
||||
private boolean tickEntities(WorldServer world) {
|
||||
try {
|
||||
world.timings.tickEntities.startTiming();
|
||||
world.tickEntities();
|
||||
world.timings.tickEntities.stopTiming();
|
||||
world.getTracker().updatePlayers();
|
||||
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport;
|
||||
try {
|
||||
@@ -90,7 +174,9 @@ public abstract class MixinMinecraftServer {
|
||||
|
||||
private void tickWorld(WorldServer world) {
|
||||
try {
|
||||
world.timings.doTick.startTiming();
|
||||
world.doTick();
|
||||
world.timings.doTick.stopTiming();
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport;
|
||||
try {
|
||||
@@ -104,7 +190,7 @@ public abstract class MixinMinecraftServer {
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void D() throws InterruptedException {
|
||||
public void D() throws InterruptedException, ExecutionException, CancellationException {
|
||||
Runnable runnable;
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
@@ -130,75 +216,108 @@ public abstract class MixinMinecraftServer {
|
||||
ChunkIOExecutor.tick();
|
||||
MinecraftTimings.chunkIOTickTimer.stopTiming();
|
||||
|
||||
Akari.worldTiming.startTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
worlds.get(i).timings.tickEntities.startTiming();
|
||||
worlds.get(i).timings.doTick.startTiming();
|
||||
}
|
||||
}
|
||||
Akari.silentTiming = true; // Disable timings
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
// Never tick one world concurrently!
|
||||
// TODO better treat world index
|
||||
for (int i = 1; i <= worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i < worlds.size() ? i : 0);
|
||||
synchronized (world) {
|
||||
if (cachedWorldSize != worlds.size()) Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||
switch (AkarinGlobalConfig.parallelMode) {
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
// Never tick one world concurrently!
|
||||
for (int i = 0; i < cachedWorldSize; i++) {
|
||||
// Impl Note:
|
||||
// Entities ticking: index 1 -> ... -> 0 (parallel)
|
||||
// World ticking: index 0 -> ... (parallel)
|
||||
int interlace = i + 1;
|
||||
WorldServer entityWorld = worlds.get(interlace < cachedWorldSize ? interlace : 0);
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinWorldServer) entityWorld).lock()) {
|
||||
tickEntities(entityWorld);
|
||||
}
|
||||
}, null/*new TimingSignal(entityWorld, true)*/);
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode != 1) {
|
||||
int fi = i;
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
WorldServer world = worlds.get(fi);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode == 1)
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 0; i < cachedWorldSize; i++) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize + 1 : cachedWorldSize * 2); i --> 0 ;) {
|
||||
Akari.STAGE_TICK.take();
|
||||
}
|
||||
|
||||
/* for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize : cachedWorldSize * 2); i --> 0 ;) {
|
||||
long startTiming = System.nanoTime();
|
||||
TimingSignal signal = Akari.STAGE_TICK.take().get();
|
||||
IMixinTimingHandler timing = (IMixinTimingHandler) (signal.isEntities ? signal.tickedWorld.timings.tickEntities : signal.tickedWorld.timings.doTick);
|
||||
timing.stopTiming(startTiming); // The overlap will be ignored
|
||||
} */
|
||||
|
||||
break;
|
||||
case 0:
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 1; i <= cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i < cachedWorldSize ? i : 0);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickEntities(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
Akari.STAGE_TICK.take();
|
||||
Akari.STAGE_TICK.take();
|
||||
break;
|
||||
case -1:
|
||||
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
tickWorld(world);
|
||||
tickEntities(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (world) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}
|
||||
|
||||
Akari.STAGE_TICK.take();
|
||||
Akari.silentTiming = false; // Enable timings
|
||||
Akari.worldTiming.stopTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
worlds.get(i).timings.tickEntities.stopTiming();
|
||||
worlds.get(i).timings.doTick.startTiming();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.stopTiming();
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
tickConflictSync(world);
|
||||
|
||||
world.getTracker().updatePlayers();
|
||||
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||
}
|
||||
|
||||
MinecraftTimings.connectionTimer.startTiming();
|
||||
this.an().c();
|
||||
serverConnection().c();
|
||||
MinecraftTimings.connectionTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.playerListTimer.startTiming();
|
||||
this.v.tick();
|
||||
MinecraftTimings.playerListTimer.stopTiming();
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.stopTiming();
|
||||
|
||||
MinecraftTimings.commandFunctionsTimer.startTiming();
|
||||
this.aL().e();
|
||||
functionManager().e();
|
||||
MinecraftTimings.commandFunctionsTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.tickablesTimer.startTiming();
|
||||
for (int i = 0; i < this.o.size(); ++i) {
|
||||
this.o.get(i).e();
|
||||
for (int i = 0; i < this.tickables.size(); ++i) {
|
||||
tickables.get(i).e();
|
||||
}
|
||||
MinecraftTimings.tickablesTimer.stopTiming();
|
||||
}
|
||||
|
||||
public void tickConflictSync(WorldServer world) {
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import co.aikar.timings.MinecraftTimings;
|
||||
import co.aikar.timings.Timing;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import net.minecraft.server.CancelledPacketHandleException;
|
||||
import net.minecraft.server.IAsyncTaskHandler;
|
||||
import net.minecraft.server.Packet;
|
||||
import net.minecraft.server.PacketListener;
|
||||
import net.minecraft.server.PlayerConnectionUtils;
|
||||
|
||||
@Mixin(value = PlayerConnectionUtils.class, remap = false)
|
||||
public abstract class MixinPlayerConnectionUtils {
|
||||
@Overwrite
|
||||
public static <T extends PacketListener> void ensureMainThread(final Packet<T> packet, final T listener, IAsyncTaskHandler iasynctaskhandler) throws CancelledPacketHandleException {
|
||||
if (!iasynctaskhandler.isMainThread()) {
|
||||
Timing timing = MinecraftTimings.getPacketTiming(packet);
|
||||
// MinecraftServer#postToMainThread inlined thread check, no twice
|
||||
Akari.callbackQueue.add(() -> {
|
||||
try (Timing ignored = timing.startTiming()) {
|
||||
packet.a(listener);
|
||||
}
|
||||
});
|
||||
throw CancelledPacketHandleException.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false)
|
||||
public class MixinTimingHandler {
|
||||
@Shadow @Final String name;
|
||||
@Shadow private boolean enabled;
|
||||
@Shadow private volatile long start;
|
||||
@Shadow private volatile int timingDepth;
|
||||
|
||||
@Overwrite
|
||||
public void stopTimingIfSync() {
|
||||
if (Akari.isPrimaryThread()) { // Akarin
|
||||
stopTiming(true); // Avoid twice thread check
|
||||
}
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void stopTiming() {
|
||||
stopTiming(false);
|
||||
}
|
||||
|
||||
@Shadow void addDiff(long diff) {}
|
||||
|
||||
public void stopTiming(boolean sync) {
|
||||
if (enabled && --timingDepth == 0 && start != 0) {
|
||||
if (Akari.silentTiming) { // It must be off-main thread now
|
||||
start = 0;
|
||||
return;
|
||||
} else {
|
||||
if (!sync && !Akari.isPrimaryThread()) { // Akarin
|
||||
if (AkarinGlobalConfig.silentAsyncTimings) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||
new Throwable().printStackTrace();
|
||||
}
|
||||
start = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
addDiff(System.nanoTime() - start);
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,58 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.defaults.VersionCommand;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MCUtil;
|
||||
|
||||
@Mixin(value = VersionCommand.class, remap = false)
|
||||
public class MixinVersionCommand {
|
||||
@Shadow private static int getFromRepo(String repo, String hash) { return 0; }
|
||||
public abstract class MixinVersionCommand {
|
||||
@Overwrite
|
||||
private static int getFromRepo(String repo, String hash) {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/ver/1.12.2..." + hash).openConnection();
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit
|
||||
try (
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))
|
||||
) {
|
||||
JSONObject obj = (JSONObject) new JSONParser().parse(reader);
|
||||
String status = (String) obj.get("status");
|
||||
switch (status) {
|
||||
case "identical":
|
||||
return 0;
|
||||
case "behind":
|
||||
return ((Number) obj.get("behind_by")).intValue();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} catch (ParseException | NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match current version with repository and calculate the distance
|
||||
@@ -59,6 +96,9 @@ public class MixinVersionCommand {
|
||||
// This should be lying in 'obtainVersion' method, but bump for faster returning
|
||||
if (customVersion) return;
|
||||
|
||||
synchronized (versionWaiters) {
|
||||
versionWaiters.add(sender);
|
||||
}
|
||||
if (versionObtaining) return;
|
||||
// The volatile guarantees the safety between different threads.
|
||||
// Remembers that we are still on main thread now,
|
||||
@@ -70,7 +110,7 @@ public class MixinVersionCommand {
|
||||
|
||||
if (hasVersion) {
|
||||
long current = System.currentTimeMillis();
|
||||
if (current - lastCheckMillis > 7200000 /* 2 hours */) {
|
||||
if (current - lastCheckMillis > AkarinGlobalConfig.versionUpdateInterval) {
|
||||
lastCheckMillis = current;
|
||||
hasVersion = false;
|
||||
} else {
|
||||
@@ -100,11 +140,9 @@ public class MixinVersionCommand {
|
||||
// We post all things because a custom version is rare (expiring is not rare),
|
||||
// and we'd better post this task as early as we can, since it's a will (horrible destiny).
|
||||
MCUtil.scheduleAsyncTask(() -> {
|
||||
// This should be lying in 'sendVersion' method, but comes here for relax main thread
|
||||
versionWaiters.add(sender);
|
||||
sender.sendMessage("Checking version, please wait...");
|
||||
|
||||
String version = Bukkit.getVersion();
|
||||
String version = Akari.getServerVersion();
|
||||
if (version == null) {
|
||||
version = "Unique"; // Custom - > Unique
|
||||
customVersion = true;
|
||||
@@ -140,9 +178,11 @@ public class MixinVersionCommand {
|
||||
versionMessage = message;
|
||||
hasVersion = true;
|
||||
|
||||
for (CommandSender sender : versionWaiters) {
|
||||
sender.sendMessage(versionMessage);
|
||||
synchronized (versionWaiters) {
|
||||
for (CommandSender sender : versionWaiters) {
|
||||
sender.sendMessage(versionMessage);
|
||||
}
|
||||
versionWaiters.clear();
|
||||
}
|
||||
versionWaiters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.server.AxisAlignedBB;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
/**
|
||||
* Fixes MC-103516(https://bugs.mojang.com/browse/MC-103516)
|
||||
*/
|
||||
@Mixin(value = World.class, remap = false)
|
||||
public abstract class MixinWorld {
|
||||
@Shadow public abstract List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB box);
|
||||
|
||||
/**
|
||||
* Returns true if there are no solid, live entities in the specified AxisAlignedBB, excluding the given entity
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean a(AxisAlignedBB box, @Nullable Entity target) { // OBFHELPER: checkNoEntityCollision
|
||||
List<Entity> list = this.getEntities(null, box);
|
||||
|
||||
for (Entity each : list) {
|
||||
if (!each.dead && each.i && each != target && (target == null || !each.x(target))) { // OBFHELPER: preventEntitySpawning - isRidingSameEntity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.WorldManager;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = WorldManager.class, remap = false)
|
||||
public abstract class MixinWorldManager {
|
||||
@Shadow @Final private WorldServer world;
|
||||
|
||||
@Overwrite
|
||||
public void a(Entity entity) {
|
||||
this.world.getTracker().entriesLock.writeLock().lock(); // Akarin
|
||||
this.world.getTracker().track(entity);
|
||||
this.world.getTracker().entriesLock.writeLock().unlock(); // Akarin
|
||||
|
||||
if (entity instanceof EntityPlayer) {
|
||||
this.world.worldProvider.a((EntityPlayer) entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false)
|
||||
public abstract class MixinWorldServer implements IMixinWorldServer {
|
||||
private final Object tickLock = new Object();
|
||||
|
||||
@Override
|
||||
public Object lock() {
|
||||
return tickLock;
|
||||
}
|
||||
|
||||
private final Random sharedRandom = new io.akarin.api.internal.utils.random.LightRandom() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private boolean locked = false;
|
||||
@Override
|
||||
public synchronized void setSeed(long seed) {
|
||||
if (locked) {
|
||||
LogManager.getLogger().error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable());
|
||||
} else {
|
||||
super.setSeed(seed);
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Random rand() {
|
||||
return sharedRandom;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.Block;
|
||||
import net.minecraft.server.Blocks;
|
||||
import net.minecraft.server.ItemMonsterEgg;
|
||||
|
||||
@Mixin(value = ItemMonsterEgg.class, remap = false)
|
||||
public class MonsterEggGuardian {
|
||||
|
||||
@Redirect(method = "a", at = @At(
|
||||
value = "FIELD",
|
||||
target = "net/minecraft/server/Blocks.MOB_SPAWNER:Lnet/minecraft/server/Block;",
|
||||
opcode = Opcodes.GETSTATIC
|
||||
))
|
||||
private boolean configurable(Block target) {
|
||||
return target == Blocks.MOB_SPAWNER && AkarinGlobalConfig.allowSpawnerModify;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.cps;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
private Chunk[] neighborChunks = new Chunk[4];
|
||||
private static final EnumDirection[] CARDINAL_DIRECTIONS = new EnumDirection[] {EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.EAST, EnumDirection.WEST};
|
||||
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
|
||||
@Override
|
||||
public Chunk getNeighborChunk(int index) {
|
||||
return this.neighborChunks[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeighborChunk(int index, @Nullable Chunk chunk) {
|
||||
this.neighborChunks[index] = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Chunk> getNeighbors() {
|
||||
List<Chunk> neighborList = Lists.newArrayList();
|
||||
for (Chunk neighbor : this.neighborChunks) {
|
||||
if (neighbor != null) {
|
||||
neighborList.add(neighbor);
|
||||
}
|
||||
}
|
||||
return neighborList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areNeighborsLoaded() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (this.neighborChunks[i] == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int directionToIndex(EnumDirection direction) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
return 0;
|
||||
case SOUTH:
|
||||
return 1;
|
||||
case EAST:
|
||||
return 2;
|
||||
case WEST:
|
||||
return 3;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected direction");
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "addEntities", at = @At("RETURN"))
|
||||
public void onLoadReturn(CallbackInfo ci) {
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), locX, locZ);
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, neighbor);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, (Chunk) (Object) this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "removeEntities", at = @At("RETURN"))
|
||||
public void onUnload(CallbackInfo ci) {
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), locX, locZ);
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, null);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package io.akarin.server.mixin.cps;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.spigotmc.SlackActivityAccountant;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.ChunkProviderServer;
|
||||
import net.minecraft.server.IChunkLoader;
|
||||
@@ -19,13 +21,10 @@ public abstract class MixinChunkProviderServer {
|
||||
@Shadow @Final public WorldServer world;
|
||||
@Shadow public Long2ObjectOpenHashMap<Chunk> chunks;
|
||||
|
||||
public int pendingUnloadChunks; // For keeping unload target-size feature
|
||||
|
||||
public void unload(Chunk chunk) {
|
||||
if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) {
|
||||
// Akarin - avoid using the queue and simply check the unloaded flag during unloads
|
||||
// this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)));
|
||||
pendingUnloadChunks++;
|
||||
chunk.setShouldUnload(true);
|
||||
}
|
||||
}
|
||||
@@ -37,43 +36,49 @@ public abstract class MixinChunkProviderServer {
|
||||
@Overwrite
|
||||
public boolean unloadChunks() {
|
||||
if (!this.world.savingDisabled) {
|
||||
long now = System.currentTimeMillis();
|
||||
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
||||
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
|
||||
activityAccountant.startActivity(0.5);
|
||||
|
||||
Iterator<Chunk> it = chunks.values().iterator();
|
||||
long now = System.currentTimeMillis();
|
||||
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
||||
int targetSize = Math.min(pendingUnloadChunks - 100, (int) (pendingUnloadChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
||||
ObjectIterator<Entry<Chunk>> it = chunks.long2ObjectEntrySet().fastIterator();
|
||||
int remainingChunks = chunks.size();
|
||||
int targetSize = Math.min(remainingChunks - 100, (int) (remainingChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
||||
|
||||
for (int i = 0; i < chunks.size() && pendingUnloadChunks > targetSize; i++) { // CraftBukkit removes unload logic to its method, we must check index
|
||||
Chunk chunk = it.next();
|
||||
while (it.hasNext()) {
|
||||
Entry<Chunk> entry = it.next();
|
||||
Chunk chunk = entry.getValue();
|
||||
|
||||
if (chunk != null && chunk.isUnloading()) {
|
||||
if (unloadAfter > 0) {
|
||||
// We changed Paper's delay unload logic, the original behavior is just mark as unloading
|
||||
if (chunk.scheduledForUnload == null || now - chunk.scheduledForUnload < unloadAfter) {
|
||||
continue;
|
||||
}
|
||||
chunk.scheduledForUnload = null;
|
||||
if (chunk.scheduledForUnload != null) {
|
||||
if (now - chunk.scheduledForUnload <= unloadAfter) continue;
|
||||
}
|
||||
|
||||
// If a plugin cancelled it, we shouldn't trying unload it for a while
|
||||
chunk.setShouldUnload(false); // Paper
|
||||
if (unloadChunk(chunk, true)) {
|
||||
it.remove();
|
||||
}
|
||||
chunk.setShouldUnload(false);
|
||||
chunk.scheduledForUnload = null;
|
||||
|
||||
if (!unloadChunk(chunk, true)) continue; // Event cancelled
|
||||
pendingUnloadChunks--;
|
||||
|
||||
if (activityAccountant.activityTimeIsExhausted()) break;
|
||||
if (--remainingChunks <= targetSize && activityAccountant.activityTimeIsExhausted()) break;
|
||||
}
|
||||
}
|
||||
activityAccountant.endActivity();
|
||||
this.chunkLoader.b(); // PAIL: chunkTick
|
||||
this.chunkLoader.b(); // OBFHELPER: chunkTick
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Redirect(method = "unloadChunk", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "it/unimi/dsi/fastutil/longs/Long2ObjectOpenHashMap.remove(J)Ljava/lang/Object;"
|
||||
))
|
||||
private Object remove(Long2ObjectOpenHashMap<Chunk> chunks, long chunkHash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public String getName() {
|
||||
return "ServerChunkCache: " + chunks.size(); // Akarin - remove unload queue
|
||||
return "ServerChunkCache: " + chunks.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,18 @@ import java.util.Set;
|
||||
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.PlayerChunk;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = CraftWorld.class, remap = false)
|
||||
public class MixinCraftWorld {
|
||||
@Inject(method = "processChunkGC()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/ChunkProviderServer.unload(Lnet/minecraft/server/Chunk;)V"
|
||||
))
|
||||
public void cancelUnloading(Chunk chunk, CallbackInfo ci) {
|
||||
if (chunk.isUnloading()) ci.cancel();
|
||||
}
|
||||
public abstract class MixinCraftWorld {
|
||||
@Shadow @Final private WorldServer world;
|
||||
|
||||
@Redirect(method = "processChunkGC()V", at = @At(
|
||||
value = "INVOKE",
|
||||
@@ -38,15 +32,8 @@ public class MixinCraftWorld {
|
||||
opcode = Opcodes.INVOKEINTERFACE
|
||||
))
|
||||
public boolean regenChunk(Set<Long> set, Object chunkHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Inject(method = "processChunkGC()V", at = @At(
|
||||
value = "FIELD",
|
||||
target = "net/minecraft/server/PlayerChunk.chunk:Lnet/minecraft/server/Chunk;",
|
||||
opcode = Opcodes.PUTFIELD
|
||||
))
|
||||
public void noUnload(PlayerChunk playerChunk, Chunk chunk, CallbackInfo ci) {
|
||||
chunk.setShouldUnload(false);
|
||||
Chunk chunk = world.getChunkProviderServer().chunks.get(chunkHash);
|
||||
if (chunk != null) chunk.setShouldUnload(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,641 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import io.akarin.api.mixin.IMixinWorldServer;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Blocks;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.ChunkSection;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.TileEntity;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.BlockPosition.MutableBlockPosition;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false, priority = 1001)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
|
||||
// Keeps track of block positions in this chunk currently queued for sky light update
|
||||
private CopyOnWriteArrayList<Short> queuedSkyLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
// Keeps track of block positions in this chunk currently queued for block light update
|
||||
private CopyOnWriteArrayList<Short> queuedBlockLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
private AtomicInteger pendingLightUpdates = new AtomicInteger();
|
||||
private long lightUpdateTime;
|
||||
private ExecutorService lightExecutorService;
|
||||
|
||||
@Shadow(aliases = "m") private boolean isGapLightingUpdated;
|
||||
@Shadow(aliases = "r") private boolean ticked;
|
||||
@Shadow @Final private ChunkSection[] sections;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int[] heightMap;
|
||||
/** Which columns need their skylightMaps updated. */
|
||||
@Shadow(aliases = "i") @Final private boolean[] updateSkylightColumns;
|
||||
/** Queue containing the BlockPosition of tile entities queued for creation */
|
||||
@Shadow(aliases = "y") @Final private ConcurrentLinkedQueue<BlockPosition> tileEntityPosQueue;
|
||||
/** Boolean value indicating if the terrain is populated. */
|
||||
@Shadow(aliases = "done") private boolean isTerrainPopulated;
|
||||
@Shadow(aliases = "lit") private boolean isLightPopulated;
|
||||
/** Lowest value in the heightmap. */
|
||||
@Shadow(aliases = "v") private int heightMapMinimum;
|
||||
|
||||
@Shadow(aliases = "b") public abstract int getHeightValue(int x, int z);
|
||||
@Shadow(aliases = "g") @Nullable public abstract TileEntity createNewTileEntity(BlockPosition pos);
|
||||
@Shadow(aliases = "a") @Nullable public abstract TileEntity getTileEntity(BlockPosition pos, Chunk.EnumTileEntityState state);
|
||||
@Shadow @Final public abstract IBlockData getBlockData(BlockPosition pos);
|
||||
@Shadow @Final public abstract IBlockData getBlockData(int x, int y, int z);
|
||||
@Shadow public abstract boolean isUnloading();
|
||||
/** Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary */
|
||||
@Shadow(aliases = "b") public abstract void checkSkylightNeighborHeight(int x, int z, int maxValue);
|
||||
@Shadow(aliases = "a") public abstract void updateSkylightNeighborHeight(int x, int z, int startY, int endY);
|
||||
@Shadow(aliases = "z") public abstract void setSkylightUpdated();
|
||||
@Shadow(aliases = "g") public abstract int getTopFilledSegment();
|
||||
@Shadow public abstract void markDirty();
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
public void onConstruct(World worldIn, int x, int z, CallbackInfo ci) {
|
||||
this.lightExecutorService = ((IMixinWorldServer) worldIn).getLightingExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtomicInteger getPendingLightUpdates() {
|
||||
return this.pendingLightUpdates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLightUpdateTime() {
|
||||
return this.lightUpdateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightUpdateTime(long time) {
|
||||
this.lightUpdateTime = time;
|
||||
}
|
||||
|
||||
@Inject(method = "b(Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onTickHead(boolean skipRecheckGaps, CallbackInfo ci) {
|
||||
final List<Chunk> neighbors = this.getSurroundingChunks();
|
||||
if (this.isGapLightingUpdated && this.world.worldProvider.m() && !skipRecheckGaps && !neighbors.isEmpty()) { // PAIL: isGapLightingUpdated - hasSkyLight
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.recheckGapsAsync(neighbors);
|
||||
});
|
||||
this.isGapLightingUpdated = false;
|
||||
}
|
||||
|
||||
this.ticked = true;
|
||||
|
||||
if (!this.isLightPopulated && this.isTerrainPopulated && !neighbors.isEmpty()) {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighbors);
|
||||
});
|
||||
// set to true to avoid requeuing the same task when not finished
|
||||
this.isLightPopulated = true;
|
||||
}
|
||||
|
||||
while (!this.tileEntityPosQueue.isEmpty()) {
|
||||
BlockPosition blockpos = this.tileEntityPosQueue.poll();
|
||||
|
||||
if (this.getTileEntity(blockpos, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockpos).getBlock().isTileEntity()) { // PAIL: getTileEntity
|
||||
TileEntity tileentity = this.createNewTileEntity(blockpos);
|
||||
this.world.setTileEntity(blockpos, tileentity);
|
||||
this.world.b(blockpos, blockpos); // PAIL: markBlockRangeForRenderUpdate
|
||||
}
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Redirect(method = "b(III)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getHighestBlockYAt(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/BlockPosition;"))
|
||||
private BlockPosition onCheckSkylightGetHeight(World world, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return BlockPosition.ZERO;
|
||||
}
|
||||
|
||||
return new BlockPosition(pos.getX(), chunk.b(pos.getX() & 15, pos.getZ() & 15), pos.getZ()); // PAIL: getHeightValue
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.areChunksLoaded(Lnet/minecraft/server/BlockPosition;I)Z"))
|
||||
private boolean onAreaLoadedSkyLightNeighbor(World world, BlockPosition pos, int radius) {
|
||||
return this.isAreaLoaded();
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.c(Lnet/minecraft/server/EnumSkyBlock;Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightForSkylightNeighbor(World world, EnumSkyBlock enumSkyBlock, BlockPosition pos) {
|
||||
return this.checkWorldLightFor(enumSkyBlock, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechecks chunk gaps async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void recheckGapsAsync(List<Chunk> neighbors) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (this.updateSkylightColumns[i + j * 16]) {
|
||||
this.updateSkylightColumns[i + j * 16] = false;
|
||||
int k = this.getHeightValue(i, j);
|
||||
int l = this.locX * 16 + i;
|
||||
int i1 = this.locZ * 16 + j;
|
||||
int j1 = Integer.MAX_VALUE;
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
final Chunk chunk = this.getLightChunk((l + enumfacing.getAdjacentX()) >> 4, (i1 + enumfacing.getAdjacentZ()) >> 4, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
continue;
|
||||
}
|
||||
j1 = Math.min(j1, chunk.w()); // PAIL: getLowestHeight
|
||||
}
|
||||
|
||||
this.checkSkylightNeighborHeight(l, i1, j1);
|
||||
|
||||
for (EnumDirection enumfacing1 : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.checkSkylightNeighborHeight(l + enumfacing1.getAdjacentX(), i1 + enumfacing1.getAdjacentZ(), k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this.isGapLightingUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getType(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/IBlockData;"))
|
||||
private IBlockData onRelightChecksGetBlockData(World world, BlockPosition pos) {
|
||||
Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), pos.getX() >> 4, pos.getZ() >> 4);
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) chunk;
|
||||
if (chunk == null || chunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return Blocks.AIR.getBlockData();
|
||||
}
|
||||
|
||||
return chunk.getBlockData(pos);
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onRelightChecksCheckLight(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
// Avoids grabbing chunk async during light check
|
||||
@Redirect(method = "e(II)Z", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightWorld(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
@Inject(method = "o()V", at = @At("HEAD"), cancellable = true)
|
||||
private void checkLightHead(CallbackInfo ci) {
|
||||
if (this.world.getMinecraftServer().isStopped() || this.lightExecutorService.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUnloading()) {
|
||||
return;
|
||||
}
|
||||
final List<Chunk> neighborChunks = this.getSurroundingChunks();
|
||||
if (neighborChunks.isEmpty()) {
|
||||
this.isLightPopulated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Akari.isPrimaryThread()) { // Akarin
|
||||
try {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
});
|
||||
} catch (RejectedExecutionException ex) {
|
||||
// This could happen if ServerHangWatchdog kills the server
|
||||
// between the start of the method and the execute() call.
|
||||
if (!this.world.getMinecraftServer().isStopped() && !this.lightExecutorService.isShutdown()) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void checkLightAsync(List<Chunk> neighbors) {
|
||||
this.isTerrainPopulated = true;
|
||||
this.isLightPopulated = true;
|
||||
BlockPosition blockpos = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
label44:
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (!this.checkLightAsync(i, j, neighbors)) {
|
||||
this.isLightPopulated = false;
|
||||
break label44;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isLightPopulated) {
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
int k = enumfacing.c() == EnumDirection.EnumAxisDirection.POSITIVE ? 16 : 1; // PAIL: getAxisDirection
|
||||
final BlockPosition pos = blockpos.shift(enumfacing, k);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
chunk.a(enumfacing.opposite()); // PAIL: checkLightSide
|
||||
}
|
||||
|
||||
this.setSkylightUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param x The x position of chunk
|
||||
* @param z The z position of chunk
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkLightAsync(int x, int z, List<Chunk> neighbors) {
|
||||
int i = this.getTopFilledSegment();
|
||||
boolean flag = false;
|
||||
boolean flag1 = false;
|
||||
MutableBlockPosition blockpos$mutableblockpos = new MutableBlockPosition((this.locX << 4) + x, 0, (this.locZ << 4) + z);
|
||||
|
||||
for (int j = i + 16 - 1; j > this.world.getSeaLevel() || j > 0 && !flag1; --j) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), j, blockpos$mutableblockpos.getZ());
|
||||
int k = this.getBlockData(blockpos$mutableblockpos).c(); // PAIL: getLightOpacity
|
||||
|
||||
if (k == 255 && blockpos$mutableblockpos.getY() < this.world.getSeaLevel()) {
|
||||
flag1 = true;
|
||||
}
|
||||
|
||||
if (!flag && k > 0) {
|
||||
flag = true;
|
||||
} else if (flag && k == 0 && !this.checkWorldLight(blockpos$mutableblockpos, neighbors)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int l = blockpos$mutableblockpos.getY(); l > 0; --l) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), l, blockpos$mutableblockpos.getZ());
|
||||
|
||||
if (this.getBlockData(blockpos$mutableblockpos).d() > 0) { // getLightValue
|
||||
this.checkWorldLight(blockpos$mutableblockpos, neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe method to retrieve a chunk during async light updates.
|
||||
*
|
||||
* @param chunkX The x position of chunk.
|
||||
* @param chunkZ The z position of chunk.
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return The chunk if available, null if not
|
||||
*/
|
||||
private Chunk getLightChunk(int chunkX, int chunkZ, List<Chunk> neighbors) {
|
||||
final Chunk currentChunk = (Chunk) (Object) this;
|
||||
if (currentChunk.a(chunkX, chunkZ)) { // PAIL: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
if (neighbors == null) {
|
||||
neighbors = this.getSurroundingChunks();
|
||||
if (neighbors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(chunkX, chunkZ)) { // PAIL: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if surrounding chunks are loaded thread-safe.
|
||||
*
|
||||
* @return True if surrounded chunks are loaded, false if not
|
||||
*/
|
||||
private boolean isAreaLoaded() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets surrounding chunks thread-safe.
|
||||
*
|
||||
* @return The list of surrounding chunks, empty list if not loaded
|
||||
*/
|
||||
private List<Chunk> getSurroundingChunks() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Chunk> chunkList = Lists.newArrayList();
|
||||
chunkList = this.getNeighbors();
|
||||
chunkList.add(southEastChunk);
|
||||
chunkList.add(southWestChunk);
|
||||
chunkList.add(northEastChunk);
|
||||
chunkList.add(northWestChunk);
|
||||
return chunkList;
|
||||
}
|
||||
|
||||
@Inject(method = "c(III)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onRelightBlock(int x, int y, int z, CallbackInfo ci) {
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.relightBlockAsync(x, y, z);
|
||||
});
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relight's a block async.
|
||||
*
|
||||
* @param x The x position
|
||||
* @param y The y position
|
||||
* @param z The z position
|
||||
*/
|
||||
private void relightBlockAsync(int x, int y, int z) {
|
||||
int i = this.heightMap[z << 4 | x] & 255;
|
||||
int j = i;
|
||||
|
||||
if (y > i) {
|
||||
j = y;
|
||||
}
|
||||
|
||||
while (j > 0 && this.getBlockData(x, j - 1, z).c() == 0) {
|
||||
--j;
|
||||
}
|
||||
|
||||
if (j != i) {
|
||||
this.markBlocksDirtyVerticalAsync(x + this.locX * 16, z + this.locZ * 16, j, i);
|
||||
this.heightMap[z << 4 | x] = j;
|
||||
int k = this.locX * 16 + x;
|
||||
int l = this.locZ * 16 + z;
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
if (j < i) {
|
||||
for (int j1 = j; j1 < i; ++j1) {
|
||||
ChunkSection extendedblockstorage2 = this.sections[j1 >> 4];
|
||||
|
||||
if (extendedblockstorage2 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage2.a(x, j1 & 15, z, 15); // PAIL: setSkyLight
|
||||
this.world.m(new BlockPosition((this.locX << 4) + x, j1, (this.locZ << 4) + z)); // PAIL: notifyLightSet
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i1 = i; i1 < j; ++i1) {
|
||||
ChunkSection extendedblockstorage = this.sections[i1 >> 4];
|
||||
|
||||
if (extendedblockstorage != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage.a(x, i1 & 15, z, 0); // PAIL: setSkyLight
|
||||
this.world.m(new BlockPosition((this.locX << 4) + x, i1, (this.locZ << 4) + z)); // PAIL: notifyLightSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int k1 = 15;
|
||||
|
||||
while (j > 0 && k1 > 0) {
|
||||
--j;
|
||||
int i2 = this.getBlockData(x, j, z).c();
|
||||
|
||||
if (i2 == 0) {
|
||||
i2 = 1;
|
||||
}
|
||||
|
||||
k1 -= i2;
|
||||
|
||||
if (k1 < 0) {
|
||||
k1 = 0;
|
||||
}
|
||||
|
||||
ChunkSection extendedblockstorage1 = this.sections[j >> 4];
|
||||
|
||||
if (extendedblockstorage1 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage1.a(x, j & 15, z, k1); // PAIL: setSkyLight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int l1 = this.heightMap[z << 4 | x];
|
||||
int j2 = i;
|
||||
int k2 = l1;
|
||||
|
||||
if (l1 < i) {
|
||||
j2 = l1;
|
||||
k2 = i;
|
||||
}
|
||||
|
||||
if (l1 < this.heightMapMinimum) {
|
||||
this.heightMapMinimum = l1;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.updateSkylightNeighborHeight(k + enumfacing.getAdjacentX(), l + enumfacing.getAdjacentZ(), j2, k2); // PAIL: updateSkylightNeighborHeight
|
||||
}
|
||||
|
||||
this.updateSkylightNeighborHeight(k, l, j2, k2);
|
||||
}
|
||||
|
||||
this.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a vertical line of blocks as dirty async.
|
||||
* Instead of calling world directly, we pass chunk safely for async light method.
|
||||
*
|
||||
* @param x1
|
||||
* @param z1
|
||||
* @param x2
|
||||
* @param z2
|
||||
*/
|
||||
private void markBlocksDirtyVerticalAsync(int x1, int z1, int x2, int z2) {
|
||||
if (x2 > z2) {
|
||||
int i = z2;
|
||||
z2 = x2;
|
||||
x2 = i;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
for (int j = x2; j <= z2; ++j) {
|
||||
final BlockPosition pos = new BlockPosition(x1, j, z1);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, new BlockPosition(x1, j, z1), chunk);
|
||||
}
|
||||
}
|
||||
|
||||
this.world.b(x1, x2, z1, x1, z2, z1); // PAIL: markBlockRangeForRenderUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light thread-safe.
|
||||
*
|
||||
* @param lightType The type of light to check
|
||||
* @param pos The block position
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLightFor(EnumSkyBlock lightType, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((IMixinWorldServer) this.world).updateLightAsync(lightType, pos, chunk);
|
||||
}
|
||||
|
||||
private boolean checkWorldLight(BlockPosition pos) {
|
||||
return this.checkWorldLight(pos, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light async.
|
||||
*
|
||||
* @param pos The block position
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLight(BlockPosition pos, List<Chunk> neighbors) {
|
||||
boolean flag = false;
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // PAIL: hasSkyLight
|
||||
flag |= ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, pos, chunk);
|
||||
}
|
||||
|
||||
flag = flag | ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.BLOCK, pos, chunk);
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of block positions currently queued for lighting updates.
|
||||
*
|
||||
* @param type The light type
|
||||
* @return The list of queued block positions, empty if none
|
||||
*/
|
||||
@Override
|
||||
public CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type) {
|
||||
if (type == EnumSkyBlock.SKY) {
|
||||
return this.queuedSkyLightingUpdates;
|
||||
}
|
||||
return this.queuedBlockLightingUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import io.akarin.api.mixin.IMixinWorldServer;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.WorldServer;
|
||||
import net.minecraft.server.BlockPosition.PooledBlockPosition;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false)
|
||||
public abstract class MixinWorldServer extends MixinWorld implements IMixinWorldServer {
|
||||
|
||||
private static final int NUM_XZ_BITS = 4;
|
||||
private static final int NUM_SHORT_Y_BITS = 8;
|
||||
private static final short XZ_MASK = 0xF;
|
||||
private static final short Y_SHORT_MASK = 0xFF;
|
||||
|
||||
private final ExecutorService lightExecutorService = preparExecutorService();;
|
||||
|
||||
private ExecutorService preparExecutorService() {
|
||||
return AkarinGlobalConfig.asyncLightingWorkStealing ?
|
||||
Executors.newFixedThreadPool(AkarinGlobalConfig.asyncLightingThreads, new ThreadFactoryBuilder().setNameFormat("Akarin Async Light Thread").build()) : Executors.newWorkStealingPool(AkarinGlobalConfig.asyncLightingThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos) { // PAIL: checkLightFor
|
||||
return updateLightAsync(lightType, pos, null);
|
||||
}
|
||||
|
||||
public boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
// Sponge - This check is not needed as neighbors are checked in updateLightAsync
|
||||
if (false && !this.areChunksLoaded(pos, 17, false)) {
|
||||
return false;
|
||||
} else {
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = this.getLightForAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int l = this.getRawBlockLightAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int i1 = pos.getX();
|
||||
int j1 = pos.getY();
|
||||
int k1 = pos.getZ();
|
||||
|
||||
if (l > k) {
|
||||
this.J[j++] = 133152; // PAIL: lightUpdateBlockList
|
||||
} else if (l < k) {
|
||||
this.J[j++] = 133152 | k << 18; // PAIL: lightUpdateBlockList
|
||||
|
||||
while (i < j) {
|
||||
int l1 = this.J[i++]; // PAIL: lightUpdateBlockList
|
||||
int i2 = (l1 & 63) - 32 + i1;
|
||||
int j2 = (l1 >> 6 & 63) - 32 + j1;
|
||||
int k2 = (l1 >> 12 & 63) - 32 + k1;
|
||||
int l2 = l1 >> 18 & 15;
|
||||
BlockPosition blockpos = new BlockPosition(i2, j2, k2);
|
||||
int i3 = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (i3 == l2) {
|
||||
this.setLightForAsync(lightType, blockpos, 0, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (l2 > 0) {
|
||||
int j3 = Math.abs(i2 - i1); // TODO MathHelper
|
||||
int k3 = Math.abs(j2 - j1);
|
||||
int l3 = Math.abs(k2 - k1);
|
||||
|
||||
if (j3 + k3 + l3 < 17) {
|
||||
PooledBlockPosition blockpos$pooledmutableblockpos = PooledBlockPosition.aquire();
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.values()) {
|
||||
int i4 = i2 + enumfacing.getAdjacentX();
|
||||
int j4 = j2 + enumfacing.getAdjacentX();
|
||||
int k4 = k2 + enumfacing.getAdjacentX();
|
||||
blockpos$pooledmutableblockpos.setValues(i4, j4, k4);
|
||||
// Sponge start - get chunk safely
|
||||
final Chunk pooledChunk = this.getLightChunk(blockpos$pooledmutableblockpos, currentChunk, neighbors);
|
||||
if (pooledChunk == null) {
|
||||
continue;
|
||||
}
|
||||
int l4 = Math.max(1, pooledChunk.getBlockData(blockpos$pooledmutableblockpos).c()); // PAIL: getLightOpacity
|
||||
i3 = this.getLightForAsync(lightType, blockpos$pooledmutableblockpos, currentChunk, neighbors);
|
||||
// Sponge end
|
||||
|
||||
if (i3 == l2 - l4 && j < this.J.length) { // PAIL: lightUpdateBlockList
|
||||
this.J[j++] = i4 - i1 + 32 | j4 - j1 + 32 << 6 | k4 - k1 + 32 << 12 | l2 - l4 << 18; // PAIL: lightUpdateBlockList
|
||||
}
|
||||
}
|
||||
|
||||
blockpos$pooledmutableblockpos.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
}
|
||||
|
||||
while (i < j) {
|
||||
int i5 = this.J[i++]; // PAIL: lightUpdateBlockList
|
||||
int j5 = (i5 & 63) - 32 + i1;
|
||||
int k5 = (i5 >> 6 & 63) - 32 + j1;
|
||||
int l5 = (i5 >> 12 & 63) - 32 + k1;
|
||||
BlockPosition blockpos1 = new BlockPosition(j5, k5, l5);
|
||||
int i6 = this.getLightForAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int j6 = this.getRawBlockLightAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 != i6) {
|
||||
this.setLightForAsync(lightType, blockpos1, j6, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 > i6) {
|
||||
int k6 = Math.abs(j5 - i1);
|
||||
int l6 = Math.abs(k5 - j1);
|
||||
int i7 = Math.abs(l5 - k1);
|
||||
boolean flag = j < this.J.length - 6; // PAIL: lightUpdateBlockList
|
||||
|
||||
if (k6 + l6 + i7 < 17 && flag) {
|
||||
// Sponge start - use thread safe method getLightForAsync
|
||||
if (this.getLightForAsync(lightType, blockpos1.west(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.east(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 + 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.down(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.up(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 + 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.north(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - 1 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.south(), currentChunk, neighbors) < j6) {
|
||||
this.J[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 + 1 - k1 + 32 << 12); // PAIL: lightUpdateBlockList
|
||||
}
|
||||
// Sponge end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sponge start - Asynchronous light updates
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).remove((Short) this.blockPosToShort(pos));
|
||||
spongeChunk.getPendingLightUpdates().decrementAndGet();
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().decrementAndGet();
|
||||
}
|
||||
|
||||
// Sponge end
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, @Nullable Chunk currentChunk) {
|
||||
if (this.getMinecraftServer().isStopped() || this.lightExecutorService.isShutdown()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentChunk == null) {
|
||||
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
|
||||
}
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
if (currentChunk == null || currentChunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final short shortPos = this.blockPosToShort(pos);
|
||||
if (spongeChunk.getQueuedLightingUpdates(lightType).contains(shortPos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk chunk = currentChunk;
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).add(shortPos);
|
||||
spongeChunk.getPendingLightUpdates().incrementAndGet();
|
||||
spongeChunk.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
|
||||
List<Chunk> neighbors = spongeChunk.getNeighbors();
|
||||
// add diagonal chunks
|
||||
Chunk southEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
Chunk southWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
Chunk northEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
Chunk northWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (southEastChunk != null) {
|
||||
neighbors.add(southEastChunk);
|
||||
}
|
||||
if (southWestChunk != null) {
|
||||
neighbors.add(southWestChunk);
|
||||
}
|
||||
if (northEastChunk != null) {
|
||||
neighbors.add(northEastChunk);
|
||||
}
|
||||
if (northWestChunk != null) {
|
||||
neighbors.add(northWestChunk);
|
||||
}
|
||||
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().incrementAndGet();
|
||||
neighbor.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
}
|
||||
|
||||
if (Akari.isPrimaryThread()) { // Akarin
|
||||
this.lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
});
|
||||
} else {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getLightingExecutor() {
|
||||
return this.lightExecutorService;
|
||||
}
|
||||
|
||||
// Thread safe methods to retrieve a chunk during async light updates
|
||||
// Each method avoids calling getLoadedChunk and instead accesses the passed neighbor chunk list to avoid concurrency issues
|
||||
public Chunk getLightChunk(BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (currentChunk.a(pos.getX() >> 4, pos.getZ() >> 4)) { // PAIL: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(pos.getX() >> 4, pos.getZ() >> 4)) { // PAIL: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getLightForAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.getY() < 0) {
|
||||
pos = new BlockPosition(pos.getX(), 0, pos.getZ());
|
||||
}
|
||||
if (!(pos.isValidLocation())) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
|
||||
return chunk.getBrightness(lightType, pos);
|
||||
}
|
||||
|
||||
private int getRawBlockLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
final Chunk chunk = getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // PAIL: defaultLightValue
|
||||
}
|
||||
if (lightType == EnumSkyBlock.SKY && chunk.c(pos)) { // PAIL: canSeeSky
|
||||
return 15;
|
||||
} else {
|
||||
IBlockData blockState = chunk.getBlockData(pos);
|
||||
int blockLight = blockState.d(); // getLightValue
|
||||
int i = lightType == EnumSkyBlock.SKY ? 0 : blockLight;
|
||||
int j = blockState.c(); // PAIL: getLightOpacity
|
||||
|
||||
if (j >= 15 && blockLight > 0) {
|
||||
j = 1;
|
||||
}
|
||||
|
||||
if (j < 1) {
|
||||
j = 1;
|
||||
}
|
||||
|
||||
if (j >= 15) {
|
||||
return 0;
|
||||
} else if (i >= 14) {
|
||||
return i;
|
||||
} else {
|
||||
for (EnumDirection facing : EnumDirection.values()) {
|
||||
BlockPosition blockpos = pos.shift(facing);
|
||||
int k = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors) - j;
|
||||
|
||||
if (k > i) {
|
||||
i = k;
|
||||
}
|
||||
|
||||
if (i >= 14) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightForAsync(EnumSkyBlock type, BlockPosition pos, int lightValue, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.isValidLocation()) {
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk != null && !chunk.isUnloading()) {
|
||||
chunk.a(type, pos, lightValue); // PAIL: setBrightness
|
||||
this.notifyLightSet(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private short blockPosToShort(BlockPosition pos) {
|
||||
short serialized = (short) setNibble(0, pos.getX() & XZ_MASK, 0, NUM_XZ_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getY() & Y_SHORT_MASK, 1, NUM_SHORT_Y_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getZ() & XZ_MASK, 3, NUM_XZ_BITS);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies bits in an integer.
|
||||
*
|
||||
* @param num Integer to modify
|
||||
* @param data Bits of data to add
|
||||
* @param which Index of nibble to start at
|
||||
* @param bitsToReplace The number of bits to replace starting from nibble index
|
||||
* @return The modified integer
|
||||
*/
|
||||
private int setNibble(int num, int data, int which, int bitsToReplace) {
|
||||
return (num & ~(bitsToReplace << (which * 4)) | (data << (which * 4)));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package io.akarin.server.mixin.nsc;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
import net.minecraft.server.PlayerConnection;
|
||||
|
||||
@Mixin(value = PlayerConnection.class, remap = false)
|
||||
public class MixinPlayerConnection {
|
||||
@Overwrite
|
||||
private long d() {
|
||||
return System.currentTimeMillis(); // nanoTime() / 1000000L
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.spigotmc.SpigotConfig;
|
||||
@@ -17,26 +18,37 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.LocalAddress;
|
||||
import io.akarin.api.internal.LocalAddress;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import io.akarin.server.core.ChannelAdapter;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelException;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.ServerChannel;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import net.minecraft.server.ChatComponentText;
|
||||
import net.minecraft.server.EnumProtocolDirection;
|
||||
import net.minecraft.server.HandshakeListener;
|
||||
import net.minecraft.server.LegacyPingHandler;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.NetworkManager;
|
||||
import net.minecraft.server.PacketDecoder;
|
||||
import net.minecraft.server.PacketEncoder;
|
||||
import net.minecraft.server.PacketPlayOutKickDisconnect;
|
||||
import net.minecraft.server.PacketPrepender;
|
||||
import net.minecraft.server.PacketSplitter;
|
||||
import net.minecraft.server.ServerConnection;
|
||||
|
||||
@Mixin(value = ServerConnection.class, remap = false)
|
||||
public class NonblockingServerConnection {
|
||||
public abstract class NonblockingServerConnection {
|
||||
private final static Logger logger = LogManager.getLogger("NSC");
|
||||
|
||||
/**
|
||||
@@ -65,7 +77,7 @@ public class NonblockingServerConnection {
|
||||
Class<? extends ServerChannel> channelClass;
|
||||
EventLoopGroup loopGroup;
|
||||
|
||||
if (Epoll.isAvailable() && this.server.af()) { // PAIL: MinecraftServer::useNativeTransport
|
||||
if (Epoll.isAvailable() && this.server.af()) { // OBFHELPER: MinecraftServer::useNativeTransport
|
||||
channelClass = EpollServerSocketChannel.class;
|
||||
loopGroup = ServerConnection.b.c();
|
||||
logger.info("Using epoll channel type");
|
||||
@@ -75,7 +87,26 @@ public class NonblockingServerConnection {
|
||||
logger.info("Using nio channel type");
|
||||
}
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap().channel(channelClass).childHandler(ChannelAdapter.create(networkManagers)).group(loopGroup);
|
||||
ServerBootstrap bootstrap = new ServerBootstrap().channel(channelClass).childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
try {
|
||||
channel.config().setOption(ChannelOption.TCP_NODELAY, true);
|
||||
} catch (ChannelException ex) {
|
||||
;
|
||||
}
|
||||
channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30))
|
||||
.addLast("legacy_query", new LegacyPingHandler(MinecraftServer.getServer().getServerConnection()))
|
||||
.addLast("splitter", new PacketSplitter()).addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND))
|
||||
.addLast("prepender", new PacketPrepender()).addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND));
|
||||
|
||||
NetworkManager manager = new NetworkManager(EnumProtocolDirection.SERVERBOUND);
|
||||
networkManagers.add(manager);
|
||||
|
||||
channel.pipeline().addLast("packet_handler", manager);
|
||||
manager.setPacketListener(new HandshakeListener(MinecraftServer.getServer(), manager));
|
||||
}
|
||||
}).group(loopGroup);
|
||||
synchronized (endPoints) {
|
||||
data.addAll(Lists.transform(AkarinGlobalConfig.extraAddress, s -> {
|
||||
String[] info = s.split(":");
|
||||
@@ -91,7 +122,7 @@ public class NonblockingServerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
@Shadow public volatile boolean d; // PAIL: neverTerminate
|
||||
@Shadow public volatile boolean d; // OBFHELPER: neverTerminate
|
||||
/**
|
||||
* Shuts down all open endpoints
|
||||
*/
|
||||
@@ -108,15 +139,15 @@ public class NonblockingServerConnection {
|
||||
|
||||
public void processPackets(NetworkManager manager) {
|
||||
try {
|
||||
manager.a(); // PAIL: NetworkManager::processReceivedPackets
|
||||
manager.a(); // OBFHELPER: NetworkManager::processReceivedPackets
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Failed to handle packet for {}", new Object[] { manager.getSocketAddress(), ex });
|
||||
final ChatComponentText kick = new ChatComponentText("Internal server error");
|
||||
logger.warn("Failed to handle packet for {}", manager.getSocketAddress(), ex);
|
||||
final ChatComponentText message = new ChatComponentText("Internal server error");
|
||||
|
||||
manager.sendPacket(new PacketPlayOutKickDisconnect(kick), new GenericFutureListener<Future<? super Void>>() {
|
||||
manager.sendPacket(new PacketPlayOutKickDisconnect(message), new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws Exception {
|
||||
manager.close(kick);
|
||||
manager.close(message);
|
||||
}
|
||||
}, new GenericFutureListener[0]);
|
||||
manager.stopReading();
|
||||
@@ -137,7 +168,7 @@ public class NonblockingServerConnection {
|
||||
Iterator<NetworkManager> it = networkManagers.iterator();
|
||||
while (it.hasNext()) {
|
||||
NetworkManager manager = it.next();
|
||||
if (manager.h()) continue; // PAIL: NetworkManager::hasNoChannel
|
||||
if (manager.h()) continue; // OBFHELPER: NetworkManager::hasNoChannel
|
||||
|
||||
if (manager.isConnected()) {
|
||||
processPackets(manager);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.akarin.server.mixin.nsc;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.CheckedConcurrentLinkedQueue;
|
||||
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
||||
|
||||
import io.akarin.api.internal.utils.CheckedConcurrentLinkedQueue;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
@@ -21,21 +21,22 @@ import net.minecraft.server.PacketPlayOutMapChunk;
|
||||
public abstract class OptimisticNetworkManager {
|
||||
@Shadow public Channel channel;
|
||||
@Shadow(aliases = "i") @Final private Queue<NetworkManager.QueuedPacket> packets;
|
||||
@Shadow(aliases = "j") @Final private ReentrantReadWriteLock queueLock;
|
||||
@Shadow(aliases = "j") @Final private ReentrantReadWriteUpdateLock queueLock;
|
||||
|
||||
@Shadow public abstract Queue<NetworkManager.QueuedPacket> getPacketQueue();
|
||||
@Shadow public abstract void dispatchPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>>[] genericFutureListeners);
|
||||
|
||||
private static final QueuedPacket SIGNAL_PACKET = new QueuedPacket(null, null);
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final QueuedPacket SIGNAL_PACKET = new QueuedPacket(null);
|
||||
|
||||
@Overwrite
|
||||
@Overwrite // OBFHELPER: trySendQueue
|
||||
private boolean m() {
|
||||
if (this.channel != null && this.channel.isOpen()) {
|
||||
if (this.packets.isEmpty()) { // return if the packet queue is empty so that the write lock by Anti-Xray doesn't affect the vanilla performance at all
|
||||
return true;
|
||||
}
|
||||
|
||||
this.queueLock.readLock().lock();
|
||||
this.queueLock.updateLock().lock();
|
||||
try {
|
||||
while (!this.packets.isEmpty()) {
|
||||
NetworkManager.QueuedPacket packet = ((CheckedConcurrentLinkedQueue<QueuedPacket>) getPacketQueue()).poll(item -> {
|
||||
@@ -51,7 +52,7 @@ public abstract class OptimisticNetworkManager {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.queueLock.readLock().unlock();
|
||||
this.queueLock.updateLock().unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.server.AxisAlignedBB;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.Material;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = Entity.class, remap = false)
|
||||
public abstract class MixinEntity {
|
||||
@Shadow public World world;
|
||||
@Shadow public abstract AxisAlignedBB getBoundingBox();
|
||||
|
||||
private boolean isInLava;
|
||||
private int lastLavaCheck = Integer.MIN_VALUE;
|
||||
|
||||
@Overwrite // OBFHELPER: isInLava
|
||||
public boolean au() {
|
||||
/*
|
||||
* This originally comes from Migot (https://github.com/Poweruser/Migot/commit/cafbf1707107d2a3aa6232879f305975bb1f0285)
|
||||
* Thanks @Poweruser
|
||||
*/
|
||||
int currentTick = MinecraftServer.currentTick;
|
||||
if (this.lastLavaCheck != currentTick) {
|
||||
this.lastLavaCheck = currentTick;
|
||||
this.isInLava = this.world.a(this.getBoundingBox().grow(-0.10000000149011612D, -0.4000000059604645D, -0.10000000149011612D), Material.LAVA);
|
||||
}
|
||||
return this.isInLava;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.destroystokyo.paper.exception.ServerInternalException;
|
||||
|
||||
import net.minecraft.server.IDataManager;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.NBTCompressedStreamTools;
|
||||
import net.minecraft.server.NBTTagCompound;
|
||||
import net.minecraft.server.PersistentBase;
|
||||
import net.minecraft.server.PersistentCollection;
|
||||
|
||||
@Mixin(value = PersistentCollection.class, remap = false)
|
||||
public abstract class MixinPersistentCollection {
|
||||
@Shadow(aliases = "b") @Final private IDataManager dataManager;
|
||||
|
||||
@Overwrite
|
||||
private void a(PersistentBase persistentbase) {
|
||||
if (this.dataManager == null) return;
|
||||
|
||||
File file = this.dataManager.getDataFile(persistentbase.id);
|
||||
if (file == null) return;
|
||||
|
||||
NBTTagCompound nbttagcompound = new NBTTagCompound();
|
||||
nbttagcompound.set("data", persistentbase.b(new NBTTagCompound()));
|
||||
|
||||
// Akarin start
|
||||
MCUtil.scheduleAsyncTask(() -> {
|
||||
try {
|
||||
FileOutputStream fileoutputstream = new FileOutputStream(file);
|
||||
|
||||
NBTCompressedStreamTools.a(nbttagcompound, fileoutputstream);
|
||||
fileoutputstream.close();
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
ServerInternalException.reportInternalException(exception); // Paper
|
||||
}
|
||||
});
|
||||
// Akarin end
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||
import net.minecraft.server.TileEntityEnchantTable;
|
||||
|
||||
@Mixin(value = TileEntityEnchantTable.class, remap = false)
|
||||
public class MixinTileEntityEnchantTable {
|
||||
public abstract class MixinTileEntityEnchantTable {
|
||||
@Overwrite
|
||||
public void e() {} // No tickable
|
||||
}
|
||||
|
||||
@@ -0,0 +1,510 @@
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.bukkit.event.block.BlockRedstoneEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.server.BaseBlockPosition;
|
||||
import net.minecraft.server.Block;
|
||||
import net.minecraft.server.BlockDiodeAbstract;
|
||||
import net.minecraft.server.BlockObserver;
|
||||
import net.minecraft.server.BlockPiston;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.BlockRedstoneComparator;
|
||||
import net.minecraft.server.BlockRedstoneTorch;
|
||||
import net.minecraft.server.BlockRedstoneWire;
|
||||
import net.minecraft.server.BlockRepeater;
|
||||
import net.minecraft.server.Blocks;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.IBlockAccess;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.Material;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = BlockRedstoneWire.class, remap = false)
|
||||
public abstract class PandaRedstoneWire extends Block {
|
||||
|
||||
protected PandaRedstoneWire(Material material) {
|
||||
super(material);
|
||||
}
|
||||
|
||||
/** Positions that need to be turned off **/
|
||||
private List<BlockPosition> turnOff = Lists.newArrayList();
|
||||
/** Positions that need to be checked to be turned on **/
|
||||
private List<BlockPosition> turnOn = Lists.newArrayList();
|
||||
/** Positions of wire that was updated already (Ordering determines update order and is therefore required!) **/
|
||||
private final Set<BlockPosition> updatedRedstoneWire = Sets.newLinkedHashSet();
|
||||
|
||||
/** Ordered arrays of the facings; Needed for the update order.
|
||||
* I went with a vertical-first order here, but vertical last would work to.
|
||||
* However it should be avoided to update the vertical axis between the horizontal ones as this would cause unneeded directional behavior. **/
|
||||
private static final EnumDirection[] facingsHorizontal = {EnumDirection.WEST, EnumDirection.EAST, EnumDirection.NORTH, EnumDirection.SOUTH};
|
||||
private static final EnumDirection[] facingsVertical = {EnumDirection.DOWN, EnumDirection.UP};
|
||||
private static final EnumDirection[] facings = ArrayUtils.addAll(facingsVertical, facingsHorizontal);
|
||||
|
||||
/** Offsets for all surrounding blocks that need to receive updates **/
|
||||
private static final BaseBlockPosition[] surroundingBlocksOffset;
|
||||
static {
|
||||
Set<BaseBlockPosition> set = Sets.newLinkedHashSet();
|
||||
for (EnumDirection facing : facings) {
|
||||
set.add(facing.getDirectionPosition());
|
||||
}
|
||||
for (EnumDirection facing1 : facings) {
|
||||
BaseBlockPosition v1 = facing1.getDirectionPosition();
|
||||
for (EnumDirection facing2 : facings) {
|
||||
BaseBlockPosition v2 = facing2.getDirectionPosition();
|
||||
set.add(new BaseBlockPosition(v1.getX() + v2.getX(), v1.getY() + v2.getY(), v1.getZ() + v2.getZ()));
|
||||
}
|
||||
}
|
||||
set.remove(BaseBlockPosition.ZERO);
|
||||
surroundingBlocksOffset = set.toArray(new BaseBlockPosition[set.size()]);
|
||||
}
|
||||
|
||||
@Shadow(aliases = "g") private boolean canProvidePower;
|
||||
@Shadow public abstract int getPower(World world, BlockPosition pos, int strength);
|
||||
@Shadow(aliases = "b") public abstract boolean isPowerSourceAt(IBlockAccess worldIn, BlockPosition pos, EnumDirection side);
|
||||
|
||||
@Inject(method = "e", at = @At("HEAD"), cancellable = true)
|
||||
private void onUpdateSurroundingRedstone(World worldIn, BlockPosition pos, IBlockData state, CallbackInfoReturnable<IBlockData> cir) {
|
||||
this.updateSurroundingRedstone(worldIn, pos);
|
||||
cir.setReturnValue(state);
|
||||
}
|
||||
|
||||
@Inject(method = "a*", at = @At("HEAD"), cancellable = true)
|
||||
private void onCalculateCurrentChanges(World worldIn, BlockPosition pos1, BlockPosition pos2, IBlockData state, CallbackInfoReturnable<IBlockData> cir) {
|
||||
this.calculateCurrentChanges(worldIn, pos1);
|
||||
cir.setReturnValue(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates all surrounding wires and causes all needed updates
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param world World
|
||||
* @param pos Position that needs updating
|
||||
*/
|
||||
private void updateSurroundingRedstone(World world, BlockPosition pos) {
|
||||
// Recalculate the connected wires
|
||||
this.calculateCurrentChanges(world, pos);
|
||||
|
||||
// Set to collect all the updates, to only execute them once. Ordering required.
|
||||
Set<BlockPosition> blocksNeedingUpdate = Sets.newLinkedHashSet();
|
||||
|
||||
// Add the needed updates
|
||||
for (BlockPosition posi : this.updatedRedstoneWire) {
|
||||
this.addBlocksNeedingUpdate(world, posi, blocksNeedingUpdate);
|
||||
}
|
||||
// Add all other updates to keep known behaviors
|
||||
// They are added in a backwards order because it preserves a commonly used behavior with the update order
|
||||
Iterator<BlockPosition> it = Lists.newLinkedList(this.updatedRedstoneWire).descendingIterator();
|
||||
while (it.hasNext()) {
|
||||
this.addAllSurroundingBlocks(it.next(), blocksNeedingUpdate);
|
||||
}
|
||||
// Remove updates on the wires as they just were updated
|
||||
blocksNeedingUpdate.removeAll(this.updatedRedstoneWire);
|
||||
/*
|
||||
* Avoid unnecessary updates on the just updated wires A huge scale test
|
||||
* showed about 40% more ticks per second It's probably less in normal
|
||||
* usage but likely still worth it
|
||||
*/
|
||||
this.updatedRedstoneWire.clear();
|
||||
|
||||
// Execute updates
|
||||
for (BlockPosition posi : blocksNeedingUpdate) {
|
||||
world.applyPhysics(posi, (BlockRedstoneWire) (Object) this, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on or off all connected wires
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param position Position of the wire that received the update
|
||||
*/
|
||||
private void calculateCurrentChanges(World worldIn, BlockPosition position) {
|
||||
// Turn off all connected wires first if needed
|
||||
if (worldIn.getType(position).getBlock() == (BlockRedstoneWire) (Object) this) {
|
||||
turnOff.add(position);
|
||||
} else {
|
||||
// In case this wire was removed, check the surrounding wires
|
||||
this.checkSurroundingWires(worldIn, position);
|
||||
}
|
||||
|
||||
while (!turnOff.isEmpty()) {
|
||||
BlockPosition pos = turnOff.remove(0);
|
||||
if (pos == null) continue; // Akarin
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
this.canProvidePower = false;
|
||||
int blockPower = worldIn.z(pos); // OBFHELPER: isBlockIndirectlyGettingPowered
|
||||
this.canProvidePower = true;
|
||||
int wirePower = this.getSurroundingWirePower(worldIn, pos);
|
||||
|
||||
// Lower the strength as it moved a block
|
||||
wirePower--;
|
||||
int newPower = Math.max(blockPower, wirePower);
|
||||
|
||||
// Akarin start - BlockRedstoneEvent
|
||||
if (oldPower != newPower) {
|
||||
BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), oldPower, newPower);
|
||||
worldIn.getServer().getPluginManager().callEvent(event);
|
||||
newPower = event.getNewCurrent();
|
||||
}
|
||||
// Akarin end
|
||||
|
||||
// Power lowered?
|
||||
if (newPower < oldPower) {
|
||||
// If it's still powered by a direct source (but weaker) mark for turn on
|
||||
if (blockPower > 0 && !this.turnOn.contains(pos)) {
|
||||
this.turnOn.add(pos);
|
||||
}
|
||||
// Set all the way to off for now, because wires that were powered by this need to update first
|
||||
setWireState(worldIn, pos, state, 0);
|
||||
// Power rose?
|
||||
} else if (newPower > oldPower) {
|
||||
// Set new Power
|
||||
this.setWireState(worldIn, pos, state, newPower);
|
||||
}
|
||||
// Check if surrounding wires need to change based on the current/new state and add them to the lists
|
||||
this.checkSurroundingWires(worldIn, pos);
|
||||
}
|
||||
// Now all needed wires are turned off. Time to turn them on again if there is a power source.
|
||||
while (!this.turnOn.isEmpty()) {
|
||||
BlockPosition pos = this.turnOn.remove(0);
|
||||
if (pos == null) continue; // Akarin
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
this.canProvidePower = false;
|
||||
int blockPower = worldIn.z(pos); // OBFHELPER: isBlockIndirectlyGettingPowered
|
||||
this.canProvidePower = true;
|
||||
int wirePower = this.getSurroundingWirePower(worldIn, pos);
|
||||
// Lower the strength as it moved a block
|
||||
wirePower--;
|
||||
int newPower = Math.max(blockPower, wirePower);
|
||||
|
||||
// Akarin start - BlockRedstoneEvent
|
||||
BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), oldPower, newPower);
|
||||
worldIn.getServer().getPluginManager().callEvent(event);
|
||||
newPower = event.getNewCurrent();
|
||||
// Akarin end
|
||||
|
||||
if (newPower > oldPower) {
|
||||
setWireState(worldIn, pos, state, newPower);
|
||||
} else if (newPower < oldPower) {
|
||||
// Add warning
|
||||
}
|
||||
// Check if surrounding wires need to change based on the current/new state and add them to the lists
|
||||
this.checkSurroundingWires(worldIn, pos);
|
||||
}
|
||||
this.turnOff.clear();
|
||||
this.turnOn.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an wire needs to be marked for update depending on the power next to it
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position of the wire that might need to change
|
||||
* @param otherPower Power of the wire next to it
|
||||
*/
|
||||
private void addWireToList(World worldIn, BlockPosition pos, int otherPower) {
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
if (state.getBlock() == (BlockRedstoneWire) (Object) this) {
|
||||
int power = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
// Could get powered stronger by the neighbor?
|
||||
if (power < (otherPower - 1) && !this.turnOn.contains(pos)) {
|
||||
// Mark for turn on check.
|
||||
this.turnOn.add(pos);
|
||||
}
|
||||
// Should have powered the neighbor? Probably was powered by it and is in turn off phase.
|
||||
if (power > otherPower && !this.turnOff.contains(pos)) {
|
||||
// Mark for turn off check.
|
||||
this.turnOff.add(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the wires around need to get updated depending on this wires state.
|
||||
* Checks all wires below before the same layer before on top to keep
|
||||
* some more rotational symmetry around the y-axis.
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position of the wire
|
||||
*/
|
||||
private void checkSurroundingWires(World worldIn, BlockPosition pos) {
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
int ownPower = 0;
|
||||
if (state.getBlock() == (BlockRedstoneWire) (Object) this) {
|
||||
ownPower = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
}
|
||||
// Check wires on the same layer first as they appear closer to the wire
|
||||
for (EnumDirection facing : facingsHorizontal) {
|
||||
BlockPosition offsetPos = pos.shift(facing);
|
||||
if (facing.getAxis().isHorizontal()) {
|
||||
this.addWireToList(worldIn, offsetPos, ownPower);
|
||||
}
|
||||
}
|
||||
for (EnumDirection facingVertical : facingsVertical) {
|
||||
BlockPosition offsetPos = pos.shift(facingVertical);
|
||||
boolean solidBlock = worldIn.getType(offsetPos).k(); // OBFHELPER: isBlockNormalCube
|
||||
for (EnumDirection facingHorizontal : facingsHorizontal) {
|
||||
// wire can travel upwards if the block on top doesn't cut the wire (is non-solid)
|
||||
// it can travel down if the block below is solid and the block "diagonal" doesn't cut off the wire (is non-solid)
|
||||
if ((facingVertical == EnumDirection.UP && !solidBlock) || (facingVertical == EnumDirection.DOWN && solidBlock && !worldIn.getType(offsetPos.shift(facingHorizontal)).k())) { // OBFHELPER: isBlockNormalCube
|
||||
this.addWireToList(worldIn, offsetPos.shift(facingHorizontal), ownPower);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum power of the surrounding wires
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position of the asking wire
|
||||
* @return The maximum power of the wires that could power the wire at pos
|
||||
*/
|
||||
private int getSurroundingWirePower(World worldIn, BlockPosition pos) {
|
||||
int wirePower = 0;
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
BlockPosition offsetPos = pos.shift(enumfacing);
|
||||
// Wires on the same layer
|
||||
wirePower = this.getPower(worldIn, offsetPos, wirePower);
|
||||
|
||||
// Block below the wire need to be solid (Upwards diode of slabs/stairs/glowstone) and no block should cut the wire
|
||||
if(worldIn.getType(offsetPos).l() && !worldIn.getType(pos.up()).l()) { // OBFHELPER: isNormalCube
|
||||
wirePower = this.getPower(worldIn, offsetPos.up(), wirePower);
|
||||
// Only get from power below if no block is cutting the wire
|
||||
} else if (!worldIn.getType(offsetPos).l()) { // OBFHELPER: isNormalCube
|
||||
wirePower = this.getPower(worldIn, offsetPos.down(), wirePower);
|
||||
}
|
||||
}
|
||||
return wirePower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all blocks that need to receive an update from a redstone change in this position.
|
||||
* This means only blocks that actually could change.
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position of the wire
|
||||
* @param set Set to add the update positions too
|
||||
*/
|
||||
private void addBlocksNeedingUpdate(World worldIn, BlockPosition pos, Set<BlockPosition> set) {
|
||||
List<EnumDirection> connectedSides = this.getSidesToPower(worldIn, pos);
|
||||
// Add the blocks next to the wire first (closest first order)
|
||||
for (EnumDirection facing : facings) {
|
||||
BlockPosition offsetPos = pos.shift(facing);
|
||||
// canConnectTo() is not the nicest solution here as it returns true for e.g. the front of a repeater
|
||||
// canBlockBePowereFromSide catches these cases
|
||||
if (!connectedSides.contains(facing.opposite()) && facing != EnumDirection.DOWN
|
||||
&& (!facing.getAxis().isHorizontal() || canConnectToBlock(worldIn.getType(offsetPos), facing))) continue;
|
||||
if (this.canBlockBePoweredFromSide(worldIn.getType(offsetPos), facing, true))
|
||||
set.add(offsetPos);
|
||||
}
|
||||
// Later add blocks around the surrounding blocks that get powered
|
||||
for (EnumDirection facing : facings) {
|
||||
BlockPosition offsetPos = pos.shift(facing);
|
||||
if (!connectedSides.contains(facing.opposite()) && facing != EnumDirection.DOWN || !worldIn.getType(offsetPos).l()) continue; // OBFHELPER: isNormalCube
|
||||
for (EnumDirection facing1 : facings) {
|
||||
if (this.canBlockBePoweredFromSide(worldIn.getType(offsetPos.shift(facing1)), facing1, false))
|
||||
set.add(offsetPos.shift(facing1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a block can get powered from a side.
|
||||
* This behavior would better be implemented per block type as follows:
|
||||
* - return false as default. (blocks that are not affected by redstone don't need to be updated, it doesn't really hurt if they are either)
|
||||
* - return true for all blocks that can get powered from all side and change based on it (doors, fence gates, trap doors, note blocks, lamps, dropper, hopper, TNT, rails, possibly more)
|
||||
* - implement own logic for pistons, repeaters, comparators and redstone torches
|
||||
* The current implementation was chosen to keep everything in one class.
|
||||
*
|
||||
* Why is this extra check needed?
|
||||
* 1. It makes sure that many old behaviors still work (QC + Pistons).
|
||||
* 2. It prevents updates from "jumping".
|
||||
* Or rather it prevents this wire to update a block that would get powered by the next one of the same line.
|
||||
* This is to prefer as it makes understanding the update order of the wire really easy. The signal "travels" from the power source.
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param state State of the block
|
||||
* @param side Side from which it gets powered
|
||||
* @param isWire True if it's powered by a wire directly, False if through a block
|
||||
* @return True if the block can change based on the power level it gets on the given side, false otherwise
|
||||
*/
|
||||
private boolean canBlockBePoweredFromSide(IBlockData state, EnumDirection side, boolean isWire) {
|
||||
if (state.getBlock() instanceof BlockPiston && state.get(BlockPiston.FACING) == side.opposite()) {
|
||||
return false;
|
||||
}
|
||||
if (state.getBlock() instanceof BlockDiodeAbstract && state.get(BlockDiodeAbstract.FACING) != side.opposite()) {
|
||||
if (isWire && state.getBlock() instanceof BlockRedstoneComparator
|
||||
&& state.get(BlockRedstoneComparator.FACING).k() != side.getAxis() && side.getAxis().isHorizontal()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (state.getBlock() instanceof BlockRedstoneTorch) {
|
||||
if (isWire || state.get(BlockRedstoneTorch.FACING) != side) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of all horizontal sides that can get powered by a wire.
|
||||
* The list is ordered the same as the facingsHorizontal.
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position of the wire
|
||||
* @return List of all facings that can get powered by this wire
|
||||
*/
|
||||
private List<EnumDirection> getSidesToPower(World worldIn, BlockPosition pos) {
|
||||
List<EnumDirection> retval = Lists.newArrayList();
|
||||
for (EnumDirection facing : facingsHorizontal) {
|
||||
if (isPowerSourceAt(worldIn, pos, facing))
|
||||
retval.add(facing);
|
||||
}
|
||||
if (retval.isEmpty()) return Lists.newArrayList(facingsHorizontal);
|
||||
boolean northsouth = retval.contains(EnumDirection.NORTH) || retval.contains(EnumDirection.SOUTH);
|
||||
boolean eastwest = retval.contains(EnumDirection.EAST) || retval.contains(EnumDirection.WEST);
|
||||
if (northsouth) {
|
||||
retval.remove(EnumDirection.EAST);
|
||||
retval.remove(EnumDirection.WEST);
|
||||
}
|
||||
if (eastwest) {
|
||||
retval.remove(EnumDirection.NORTH);
|
||||
retval.remove(EnumDirection.SOUTH);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all surrounding positions to a set.
|
||||
* This is the neighbor blocks, as well as their neighbors
|
||||
*
|
||||
* @param pos
|
||||
* @param set
|
||||
*/
|
||||
private void addAllSurroundingBlocks(BlockPosition pos, Set<BlockPosition> set) {
|
||||
for (BaseBlockPosition vect : surroundingBlocksOffset) {
|
||||
set.add(pos.a(vect)); // OBFHELPER: add
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block state of a wire with a new power level and marks for updates
|
||||
*
|
||||
* @author panda
|
||||
*
|
||||
* @param worldIn World
|
||||
* @param pos Position at which the state needs to be set
|
||||
* @param state Old state
|
||||
* @param power Power it should get set to
|
||||
*/
|
||||
private void setWireState(World worldIn, BlockPosition pos, IBlockData state, int power) {
|
||||
state = state.set(BlockRedstoneWire.POWER, Integer.valueOf(power));
|
||||
worldIn.setTypeAndData(pos, state, 2);
|
||||
updatedRedstoneWire.add(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author panda
|
||||
* @reason Uses local surrounding block offset list for notifications.
|
||||
*
|
||||
* @param world The world
|
||||
* @param pos The position
|
||||
* @param state The block state
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void onPlace(World world, BlockPosition pos, IBlockData state) {
|
||||
this.updateSurroundingRedstone(world, pos);
|
||||
for (BaseBlockPosition vec : surroundingBlocksOffset) {
|
||||
world.applyPhysics(pos.a(vec), this, false); // OBFHELPER: add
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author panda
|
||||
* @reason Uses local surrounding block offset list for notifications.
|
||||
*
|
||||
* @param world The world
|
||||
* @param pos The position
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void remove(World world, BlockPosition pos, IBlockData state) {
|
||||
super.remove(world, pos, state);
|
||||
this.updateSurroundingRedstone(world, pos);
|
||||
for (BaseBlockPosition vec : surroundingBlocksOffset) {
|
||||
world.applyPhysics(pos.a(vec), this, false); // OBFHELPER: add
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author panda
|
||||
* @reason Changed to use getSidesToPower() to avoid duplicate implementation.
|
||||
*
|
||||
* @param blockState The block state
|
||||
* @param blockAccess The block access
|
||||
* @param pos The position
|
||||
* @param side The side
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public int b(IBlockData blockState, IBlockAccess blockAccess, BlockPosition pos, EnumDirection side) { // OBFHELPER: getWeakPower
|
||||
if (!this.canProvidePower) {
|
||||
return 0;
|
||||
} else {
|
||||
if (side == EnumDirection.UP || this.getSidesToPower((World) blockAccess, pos).contains(side)) {
|
||||
return blockState.get(BlockRedstoneWire.POWER).intValue();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canConnectToBlock(IBlockData blockState, @Nullable EnumDirection side) {
|
||||
Block block = blockState.getBlock();
|
||||
|
||||
if (block == Blocks.REDSTONE_WIRE) {
|
||||
return true;
|
||||
} else if (Blocks.UNPOWERED_REPEATER.D(blockState)) { // OBFHELPER: isSameDiode
|
||||
EnumDirection enumdirection1 = blockState.get(BlockRepeater.FACING);
|
||||
|
||||
return enumdirection1 == side || enumdirection1.opposite() == side;
|
||||
} else if (Blocks.dk == blockState.getBlock()) {
|
||||
return side == blockState.get(BlockObserver.FACING); // OBFHELPER: OBSERVER
|
||||
} else {
|
||||
return blockState.m() && side != null; // OBFHELPER: canProvidePower
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import net.minecraft.server.WorldGenBigTree;
|
||||
* Fixes MC-128547(https://bugs.mojang.com/browse/MC-128547)
|
||||
*/
|
||||
@Mixin(value = WorldGenBigTree.class, remap = false)
|
||||
public class WeakBigTree {
|
||||
public abstract class WeakBigTree {
|
||||
@Shadow(aliases = "l") private World worldReference;
|
||||
|
||||
@Inject(method = "generate", at = @At("RETURN"))
|
||||
|
||||
@@ -40,7 +40,7 @@ import net.minecraft.server.ItemStack;
|
||||
* Fixes MC-128547(https://bugs.mojang.com/browse/MC-128547)
|
||||
*/
|
||||
@Mixin(value = EnchantmentManager.class, remap = false)
|
||||
public class WeakEnchantmentManager {
|
||||
public abstract class WeakEnchantmentManager {
|
||||
@Shadow(aliases = "a") @Final private static EnchantmentManager.EnchantmentModifierProtection protection;
|
||||
@Shadow(aliases = "c") @Final private static EnchantmentManager.EnchantmentModifierThorns thorns;
|
||||
@Shadow(aliases = "d") @Final private static EnchantmentManager.EnchantmentModifierArthropods arthropods;
|
||||
@@ -50,23 +50,23 @@ public class WeakEnchantmentManager {
|
||||
|
||||
@Overwrite
|
||||
public static int a(Iterable<ItemStack> iterable, DamageSource damageSource) {
|
||||
protection.a = 0; // PAIL: damageModifier
|
||||
protection.a = 0; // OBFHELPER: damageModifier
|
||||
protection.b = damageSource;
|
||||
a(protection, iterable); // PAIL: applyEnchantmentModifierArray
|
||||
a(protection, iterable); // OBFHELPER: applyEnchantmentModifierArray
|
||||
protection.b = null; // Akarin - Remove reference to Damagesource
|
||||
return protection.a;
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public static void a(EntityLiving user, Entity attacker) { // PAIL: applyThornEnchantments
|
||||
public static void a(EntityLiving user, Entity attacker) { // OBFHELPER: applyThornEnchantments
|
||||
thorns.b = attacker;
|
||||
thorns.a = user;
|
||||
if (user != null) {
|
||||
a(thorns, user.aQ()); // PAIL: applyEnchantmentModifierArray - getEquipmentAndArmor
|
||||
a(thorns, user.aQ()); // OBFHELPER: applyEnchantmentModifierArray - getEquipmentAndArmor
|
||||
}
|
||||
|
||||
if (attacker instanceof EntityHuman) {
|
||||
a(thorns, user.getItemInMainHand()); // PAIL: applyEnchantmentModifier
|
||||
a(thorns, user.getItemInMainHand()); // OBFHELPER: applyEnchantmentModifier
|
||||
}
|
||||
|
||||
// Akarin Start - remove references to entity objects to avoid memory leaks
|
||||
@@ -76,15 +76,15 @@ public class WeakEnchantmentManager {
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public static void b(EntityLiving user, Entity target) { // PAIL: applyArthropodEnchantments
|
||||
public static void b(EntityLiving user, Entity target) { // OBFHELPER: applyArthropodEnchantments
|
||||
arthropods.a = user;
|
||||
arthropods.b = target;
|
||||
if (user != null) {
|
||||
a(arthropods, user.aQ()); // PAIL: applyEnchantmentModifierArray - getEquipmentAndArmor
|
||||
a(arthropods, user.aQ()); // OBFHELPER: applyEnchantmentModifierArray - getEquipmentAndArmor
|
||||
}
|
||||
|
||||
if (user instanceof EntityHuman) {
|
||||
a(arthropods, user.getItemInMainHand()); // PAIL: applyEnchantmentModifier
|
||||
a(arthropods, user.getItemInMainHand()); // OBFHELPER: applyEnchantmentModifier
|
||||
}
|
||||
|
||||
// Akarin Start - remove references to entity objects to avoid memory leaks
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = Entity.class, remap = false, priority = 1001)
|
||||
public abstract class MixinEntity {
|
||||
private static final String ENTITY_RIDABLE_COOLDOWN_FIELD = "Lnet/minecraft/entity/Entity;j:I"; // PUTFIELD: rideCooldown
|
||||
private static final String ENTITY_PORTAL_COUNTER_FIELD = "Lnet/minecraft/entity/Entity;al:I"; // PUTFIELD: portalCounter
|
||||
@Shadow protected int j;
|
||||
@Shadow protected int al;
|
||||
@Shadow public World world;
|
||||
|
||||
// OBFHELPER: onEntityUpdate
|
||||
@Redirect(method = "Y()V", at = @At(value = "FIELD", target = ENTITY_RIDABLE_COOLDOWN_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupEntityCooldown(Entity self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.world).getRealTimeTicks();
|
||||
this.j = Math.max(0, this.j - ticks); // OBFHELPER: rideCooldown
|
||||
}
|
||||
|
||||
@Redirect(method = "Y()V", at = @At(value = "FIELD", target = ENTITY_PORTAL_COUNTER_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupPortalCounter(Entity self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.world).getRealTimeTicks();
|
||||
this.al += ticks; // OBFHELPER: portalCounter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityAgeable;
|
||||
|
||||
@Mixin(value = EntityAgeable.class, remap = false)
|
||||
public abstract class MixinEntityAgeable {
|
||||
private static final String ENTITY_AGEABLE_SET_GROWING_AGE_METHOD = "Lnet/minecraft/entity/EntityAgeable;setAgeRaw(I)V";
|
||||
|
||||
// OBFHELPER: onLivingUpdate
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = ENTITY_AGEABLE_SET_GROWING_AGE_METHOD, ordinal = 0))
|
||||
public void fixupGrowingUp(EntityAgeable self, int age) {
|
||||
// Subtract the one the original update method added
|
||||
int diff = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks() - 1;
|
||||
self.setAgeRaw(Math.min(0, age + diff));
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = ENTITY_AGEABLE_SET_GROWING_AGE_METHOD, ordinal = 1))
|
||||
public void fixupBreedingCooldown(EntityAgeable self, int age) {
|
||||
// Subtract the one the original update method added
|
||||
int diff = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks() - 1;
|
||||
self.setAgeRaw(Math.max(0, age - diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityExperienceOrb;
|
||||
|
||||
@Mixin(value = EntityExperienceOrb.class, remap = false)
|
||||
public abstract class MixinEntityExperienceOrb {
|
||||
private static final String ENTITY_XP_DELAY_PICKUP_FIELD = "Lnet/minecraft/entity/item/EntityExperienceOrb;c:I"; // PUTFIELD: delayBeforeCanPickup
|
||||
private static final String ENTITY_XP_AGE_FIELD = "Lnet/minecraft/entity/item/EntityExperienceOrb;b:I"; // PUTFIELD: xpOrbAge
|
||||
@Shadow public int c; // OBFHELPER: delayBeforeCanPickup
|
||||
@Shadow public int b; // OBFHELPER: xpOrbAge
|
||||
|
||||
// OBFHELPER: onUpdate
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_XP_DELAY_PICKUP_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupPickupDelay(EntityExperienceOrb self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.c = Math.max(0, this.c - ticks); // OBFHELPER: delayBeforeCanPickup
|
||||
}
|
||||
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_XP_AGE_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupAge(EntityExperienceOrb self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.b += ticks; // OBFHELPER: xpOrbAge
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityHuman;
|
||||
|
||||
@Mixin(value = EntityHuman.class, remap = false)
|
||||
public abstract class MixinEntityHuman {
|
||||
private static final String ENTITY_PLAYER_XP_COOLDOWN_FIELD = "Lnet/minecraft/entity/player/EntityHuman;bD:I"; // PUTFIELD: xpCooldown
|
||||
private static final String ENTITY_PLAYER_SLEEP_TIMER_FIELD = "Lnet/minecraft/entity/player/EntityHuman;sleepTicks:I";
|
||||
@Shadow public int bD;
|
||||
@Shadow private int sleepTicks;
|
||||
|
||||
// OBFHELPER: onUpdate
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_PLAYER_XP_COOLDOWN_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupXpCooldown(EntityHuman self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.bD = Math.max(0, this.bD - ticks); // OBFHELPER: xpCooldown
|
||||
}
|
||||
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_PLAYER_SLEEP_TIMER_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupSleepTimer(EntityHuman self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.sleepTicks += ticks;
|
||||
}
|
||||
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_PLAYER_SLEEP_TIMER_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 2))
|
||||
public void fixupWakeTimer(EntityHuman self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.sleepTicks += ticks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityInsentient;
|
||||
import net.minecraft.server.EntityLiving;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = EntityInsentient.class, remap = false)
|
||||
public abstract class MixinEntityInsentient extends EntityLiving {
|
||||
private static final String ENTITY_LIVING_AGE_FIELD = "Lnet/minecraft/entity/EntityInsentient;ticksFarFromPlayer:I";
|
||||
|
||||
public MixinEntityInsentient(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Redirect(method = "doTick()V", at = @At(value = "FIELD", target = ENTITY_LIVING_AGE_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupEntityDespawnAge(EntityInsentient self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.ticksFarFromPlayer += ticks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityItem;
|
||||
|
||||
@Mixin(value = EntityItem.class, remap = false)
|
||||
public abstract class MixinEntityItem {
|
||||
private static final String ENTITY_ITEM_DELAY_PICKUP_FIELD = "Lnet/minecraft/entity/item/EntityItem;pickupDelay:I";
|
||||
private static final String ENTITY_ITEM_AGE_FIELD = "Lnet/minecraft/entity/item/EntityItem;age:I";
|
||||
@Shadow public int age;
|
||||
@Shadow private int pickupDelay;
|
||||
|
||||
// OBFHELPER: onUpdate
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_ITEM_DELAY_PICKUP_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupPickupDelay(EntityItem self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.pickupDelay = Math.max(0, this.pickupDelay - ticks);
|
||||
}
|
||||
|
||||
@Redirect(method = "B_()V", at = @At(value = "FIELD", target = ENTITY_ITEM_AGE_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupAge(EntityItem self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.age += ticks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = EntityPlayer.class, remap = false)
|
||||
public abstract class MixinEntityPlayer extends Entity {
|
||||
private static final String ENTITY_PLAYER_MP_PORTAL_COOLDOWN_FIELD = "Lnet/minecraft/entity/player/EntityPlayer;portalCooldown:I";
|
||||
|
||||
public MixinEntityPlayer(World worldIn) {
|
||||
super(worldIn);
|
||||
}
|
||||
|
||||
// OBFHELPER: decrementTimeUntilPortal
|
||||
@Redirect(method = "I()V", at = @At(value = "FIELD", target = ENTITY_PLAYER_MP_PORTAL_COOLDOWN_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupPortalCooldown(EntityPlayer self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
this.portalCooldown = Math.max(0, this.portalCooldown - ticks);
|
||||
}
|
||||
}
|
||||
@@ -22,30 +22,26 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.mixin.IMixinChunk;
|
||||
import net.minecraft.server.ChunkProviderServer;
|
||||
import net.minecraft.server.WorldServer;
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.EntityZombieVillager;
|
||||
|
||||
@Mixin(value = ChunkProviderServer.class, remap = false, priority = 1001)
|
||||
public class MixinChunkProviderServer {
|
||||
@Shadow @Final public WorldServer world;
|
||||
|
||||
@Redirect(method = "unloadChunks", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/Chunk;isUnloading()Z"
|
||||
))
|
||||
public boolean shouldUnload(IMixinChunk chunk) {
|
||||
if (chunk.getPendingLightUpdates().get() > 0 || this.world.getTime() - chunk.getLightUpdateTime() < 20) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@Mixin(value = EntityZombieVillager.class, remap = false)
|
||||
public abstract class MixinEntityZombieVillager {
|
||||
private static final String ENTITY_ZOMBIE_GET_CONVERSION_BOOST_METHOD = "Lnet/minecraft/entity/monster/EntityZombieVillager;du()I"; // INVOKE: getConversionProgress
|
||||
|
||||
@Shadow(aliases = "du") protected abstract int getConversionProgress();
|
||||
|
||||
// OBFHELPER: onUpdate
|
||||
@Redirect(method = "B_()V", at = @At(value = "INVOKE", target = ENTITY_ZOMBIE_GET_CONVERSION_BOOST_METHOD, ordinal = 0))
|
||||
public int fixupConversionTimeBoost(EntityZombieVillager self) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) self.getWorld()).getRealTimeTicks();
|
||||
return this.getConversionProgress() * ticks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
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 io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Mixin(value = MinecraftServer.class, remap = false, priority = 1001)
|
||||
public abstract class MixinMinecraftServer implements IMixinRealTimeTicking {
|
||||
private static long lastTickNanos = System.nanoTime();
|
||||
private static long realTimeTicks = 1;
|
||||
|
||||
@Inject(method = "C()V", at = @At("HEAD")) // OBFHELPER: fullTick
|
||||
public void onTickUpdateRealTimeTicks(CallbackInfo ci) {
|
||||
long currentNanos = System.nanoTime();
|
||||
realTimeTicks = (currentNanos - lastTickNanos) / 50000000;
|
||||
if (realTimeTicks < 1) {
|
||||
realTimeTicks = 1;
|
||||
}
|
||||
lastTickNanos = currentNanos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRealTimeTicks() {
|
||||
return realTimeTicks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.PlayerConnection;
|
||||
|
||||
@Mixin(value = PlayerConnection.class, remap = false)
|
||||
public abstract class MixinPlayerConnection {
|
||||
private static final String NET_HANDLER_PLAY_CHAT_SPAM_FIELD = "Lnet/minecraft/network/PlayerConnection;chatThrottle:I";
|
||||
private static final String NET_HANDLER_PLAY_DROP_SPAM_FIELD = "Lnet/minecraft/network/PlayerConnection;itemDropThreshold:I";
|
||||
@Shadow private volatile int chatThrottle;
|
||||
@Shadow(aliases = "j") private int itemDropThreshold;
|
||||
@Shadow @Final private MinecraftServer minecraftServer;
|
||||
|
||||
// OBFHELPER: update
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = NET_HANDLER_PLAY_CHAT_SPAM_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupChatSpamCheck(PlayerConnection self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.minecraftServer).getRealTimeTicks();
|
||||
this.chatThrottle = Math.max(0, this.chatThrottle - ticks);
|
||||
}
|
||||
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = NET_HANDLER_PLAY_DROP_SPAM_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupDropSpamCheck(PlayerConnection self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.minecraftServer).getRealTimeTicks();
|
||||
this.itemDropThreshold = Math.max(0, this.itemDropThreshold - ticks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.PlayerInteractManager;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = PlayerInteractManager.class, remap = false)
|
||||
public abstract class MixinPlayerInteractManager {
|
||||
private static final String PLAYER_INTERACTION_BLOCK_DAMAGE_FIELD = "Lnet/minecraft/server/management/PlayerInteractManager;currentTick:I";
|
||||
@Shadow public World world;
|
||||
@Shadow private int currentTick;
|
||||
|
||||
// OBFHELPER: updateBlockRemoving
|
||||
@Redirect(method = "a()V", at = @At(value = "FIELD", target = PLAYER_INTERACTION_BLOCK_DAMAGE_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupDiggingTime(PlayerInteractManager self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.world.getMinecraftServer()).getRealTimeTicks();
|
||||
this.currentTick += ticks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.TileEntity;
|
||||
import net.minecraft.server.TileEntityBrewingStand;
|
||||
|
||||
@Mixin(value = TileEntityBrewingStand.class, remap = false)
|
||||
public abstract class MixinTileEntityBrewingStand extends TileEntity {
|
||||
private static final String BREWING_STAND_BREW_TIME_FIELD = "Lnet/minecraft/tileentity/TileEntityBrewingStand;brewTime:I";
|
||||
@Shadow private int brewTime;
|
||||
|
||||
// OBFHELPER: update
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = BREWING_STAND_BREW_TIME_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupBrewTime(TileEntityBrewingStand self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.getWorld()).getRealTimeTicks();
|
||||
this.brewTime = Math.max(0, this.brewTime - ticks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.spongepowered.asm.lib.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.MathHelper;
|
||||
import net.minecraft.server.TileEntity;
|
||||
import net.minecraft.server.TileEntityFurnace;
|
||||
|
||||
@Mixin(value = TileEntityFurnace.class, remap = false)
|
||||
public abstract class MixinTileEntityFurnace extends TileEntity {
|
||||
private static final String FURNACE_BURN_TIME_FIELD = "Lnet/minecraft/tileentity/TileEntityFurnace;burnTime:I";
|
||||
private static final String FURNACE_COOK_TIME_FIELD = "Lnet/minecraft/tileentity/TileEntityFurnace;cookTime:I";
|
||||
@Shadow private int burnTime;
|
||||
@Shadow private int cookTime;
|
||||
@Shadow private int cookTimeTotal;
|
||||
|
||||
// OBFHELPER: update
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = FURNACE_BURN_TIME_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupBurnTime(TileEntityFurnace self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.getWorld()).getRealTimeTicks();
|
||||
this.burnTime = Math.max(0, this.burnTime - ticks);
|
||||
}
|
||||
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = FURNACE_COOK_TIME_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 0))
|
||||
public void fixupCookTime(TileEntityFurnace self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.getWorld()).getRealTimeTicks();
|
||||
this.cookTime = Math.min(this.cookTimeTotal, this.cookTime + ticks);
|
||||
}
|
||||
|
||||
@Redirect(method = "e()V", at = @At(value = "FIELD", target = FURNACE_COOK_TIME_FIELD, opcode = Opcodes.PUTFIELD, ordinal = 3))
|
||||
public void fixupCookTimeCooldown(TileEntityFurnace self, int modifier) {
|
||||
int ticks = (int) ((IMixinRealTimeTicking) this.getWorld()).getRealTimeTicks();
|
||||
this.cookTime = MathHelper.clamp(this.cookTime - (2 * ticks), 0, this.cookTimeTotal);
|
||||
}
|
||||
}
|
||||
@@ -22,24 +22,25 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IChunkProvider;
|
||||
import io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = World.class, remap = false)
|
||||
public abstract class MixinWorld {
|
||||
@Shadow protected IChunkProvider chunkProvider;
|
||||
@Shadow int[] J; // PAIL: lightUpdateBlockList
|
||||
|
||||
@Shadow(aliases = "c") public abstract boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos);
|
||||
@Shadow public abstract MinecraftServer getMinecraftServer();
|
||||
@Shadow public abstract boolean areChunksLoaded(BlockPosition center, int radius, boolean allowEmpty);
|
||||
@Shadow(aliases = "m") public abstract void notifyLightSet(BlockPosition pos);
|
||||
@Mixin(value = World.class, remap = false, priority = 1001)
|
||||
public abstract class MixinWorld implements IMixinRealTimeTicking {
|
||||
@Shadow @Nullable public abstract MinecraftServer getMinecraftServer();
|
||||
|
||||
@Override
|
||||
public long getRealTimeTicks() {
|
||||
if (this.getMinecraftServer() != null) {
|
||||
return ((IMixinRealTimeTicking) this.getMinecraftServer()).getRealTimeTicks();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.realtime;
|
||||
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
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 io.akarin.api.internal.mixin.IMixinRealTimeTicking;
|
||||
import net.minecraft.server.IDataManager;
|
||||
import net.minecraft.server.MethodProfiler;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.WorldData;
|
||||
import net.minecraft.server.WorldProvider;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false, priority = 1001)
|
||||
public abstract class MixinWorldServer extends World implements IMixinRealTimeTicking {
|
||||
|
||||
protected MixinWorldServer(IDataManager idatamanager, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag, ChunkGenerator gen, Environment env) {
|
||||
super(idatamanager, worlddata, worldprovider, methodprofiler, flag, gen, env);
|
||||
}
|
||||
|
||||
@Inject(method = "doTick()V", at = @At("HEAD"))
|
||||
public void fixTimeOfDay(CallbackInfo ci) {
|
||||
if (this.getGameRules().getBoolean("doDaylightCycle")) {
|
||||
// Subtract the one the original tick method is going to add
|
||||
long diff = this.getRealTimeTicks() - 1;
|
||||
// Don't set if we're not changing it as other mods might be listening for changes
|
||||
if (diff > 0) {
|
||||
this.worldData.setDayTime(this.worldData.getDayTime() + diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
479
sources/src/main/java/net/minecraft/server/BlockChest.java
Normal file
479
sources/src/main/java/net/minecraft/server/BlockChest.java
Normal file
@@ -0,0 +1,479 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.Iterator;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BlockChest extends BlockTileEntity {
|
||||
|
||||
public static final BlockStateDirection FACING = BlockFacingHorizontal.FACING;
|
||||
protected static final AxisAlignedBB b = new AxisAlignedBB(0.0625D, 0.0D, 0.0D, 0.9375D, 0.875D, 0.9375D);
|
||||
protected static final AxisAlignedBB c = new AxisAlignedBB(0.0625D, 0.0D, 0.0625D, 0.9375D, 0.875D, 1.0D);
|
||||
protected static final AxisAlignedBB d = new AxisAlignedBB(0.0D, 0.0D, 0.0625D, 0.9375D, 0.875D, 0.9375D);
|
||||
protected static final AxisAlignedBB e = new AxisAlignedBB(0.0625D, 0.0D, 0.0625D, 1.0D, 0.875D, 0.9375D);
|
||||
protected static final AxisAlignedBB f = new AxisAlignedBB(0.0625D, 0.0D, 0.0625D, 0.9375D, 0.875D, 0.9375D);
|
||||
public final BlockChest.Type g;
|
||||
|
||||
protected BlockChest(BlockChest.Type blockchest_type) {
|
||||
super(Material.WOOD);
|
||||
this.w(this.blockStateList.getBlockData().set(BlockChest.FACING, EnumDirection.NORTH));
|
||||
this.g = blockchest_type;
|
||||
this.a(blockchest_type == BlockChest.Type.TRAP ? CreativeModeTab.d : CreativeModeTab.c);
|
||||
}
|
||||
|
||||
public boolean b(IBlockData iblockdata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean c(IBlockData iblockdata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public EnumRenderType a(IBlockData iblockdata) {
|
||||
return EnumRenderType.ENTITYBLOCK_ANIMATED;
|
||||
}
|
||||
|
||||
public AxisAlignedBB b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) {
|
||||
return iblockaccess.getType(blockposition.north()).getBlock() == this ? BlockChest.b : (iblockaccess.getType(blockposition.south()).getBlock() == this ? BlockChest.c : (iblockaccess.getType(blockposition.west()).getBlock() == this ? BlockChest.d : (iblockaccess.getType(blockposition.east()).getBlock() == this ? BlockChest.e : BlockChest.f)));
|
||||
}
|
||||
|
||||
public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) {
|
||||
this.e(world, blockposition, iblockdata);
|
||||
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EnumDirection enumdirection = (EnumDirection) iterator.next();
|
||||
BlockPosition blockposition1 = blockposition.shift(enumdirection);
|
||||
// NeonPaper start - Dont load chunks for chests
|
||||
final IBlockData iblockdata1 = world.isLoaded(blockposition1) ? world.getType(blockposition1) : null;
|
||||
if (iblockdata1 == null) {
|
||||
continue;
|
||||
}
|
||||
// NeonPaper end
|
||||
|
||||
if (iblockdata1.getBlock() == this) {
|
||||
this.e(world, blockposition1, iblockdata1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IBlockData getPlacedState(World world, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2, int i, EntityLiving entityliving) {
|
||||
return this.getBlockData().set(BlockChest.FACING, entityliving.getDirection());
|
||||
}
|
||||
|
||||
public void postPlace(World world, BlockPosition blockposition, IBlockData iblockdata, EntityLiving entityliving, ItemStack itemstack) {
|
||||
EnumDirection enumdirection = EnumDirection.fromType2(MathHelper.floor((double) (entityliving.yaw * 4.0F / 360.0F) + 0.5D) & 3).opposite();
|
||||
|
||||
iblockdata = iblockdata.set(BlockChest.FACING, enumdirection);
|
||||
BlockPosition blockposition1 = blockposition.north();
|
||||
BlockPosition blockposition2 = blockposition.south();
|
||||
BlockPosition blockposition3 = blockposition.west();
|
||||
BlockPosition blockposition4 = blockposition.east();
|
||||
boolean flag = this == world.getType(blockposition1).getBlock();
|
||||
boolean flag1 = this == world.getType(blockposition2).getBlock();
|
||||
boolean flag2 = this == world.getType(blockposition3).getBlock();
|
||||
boolean flag3 = this == world.getType(blockposition4).getBlock();
|
||||
|
||||
if (!flag && !flag1 && !flag2 && !flag3) {
|
||||
world.setTypeAndData(blockposition, iblockdata, 3);
|
||||
} else if (enumdirection.k() == EnumDirection.EnumAxis.X && (flag || flag1)) {
|
||||
if (flag) {
|
||||
world.setTypeAndData(blockposition1, iblockdata, 3);
|
||||
} else {
|
||||
world.setTypeAndData(blockposition2, iblockdata, 3);
|
||||
}
|
||||
|
||||
world.setTypeAndData(blockposition, iblockdata, 3);
|
||||
} else if (enumdirection.k() == EnumDirection.EnumAxis.Z && (flag2 || flag3)) {
|
||||
if (flag2) {
|
||||
world.setTypeAndData(blockposition3, iblockdata, 3);
|
||||
} else {
|
||||
world.setTypeAndData(blockposition4, iblockdata, 3);
|
||||
}
|
||||
|
||||
world.setTypeAndData(blockposition, iblockdata, 3);
|
||||
}
|
||||
|
||||
if (itemstack.hasName()) {
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntityChest) {
|
||||
((TileEntityChest) tileentity).setCustomName(itemstack.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IBlockData e(World world, BlockPosition blockposition, IBlockData iblockdata) {
|
||||
if (world.isClientSide) {
|
||||
return iblockdata;
|
||||
} else {
|
||||
IBlockData iblockdata1 = world.getType(blockposition.north());
|
||||
IBlockData iblockdata2 = world.getType(blockposition.south());
|
||||
IBlockData iblockdata3 = world.getType(blockposition.west());
|
||||
IBlockData iblockdata4 = world.getType(blockposition.east());
|
||||
EnumDirection enumdirection = (EnumDirection) iblockdata.get(BlockChest.FACING);
|
||||
|
||||
if (iblockdata1.getBlock() != this && iblockdata2.getBlock() != this) {
|
||||
boolean flag = iblockdata1.b();
|
||||
boolean flag1 = iblockdata2.b();
|
||||
|
||||
if (iblockdata3.getBlock() == this || iblockdata4.getBlock() == this) {
|
||||
BlockPosition blockposition1 = iblockdata3.getBlock() == this ? blockposition.west() : blockposition.east();
|
||||
IBlockData iblockdata5 = world.getType(blockposition1.north());
|
||||
IBlockData iblockdata6 = world.getType(blockposition1.south());
|
||||
|
||||
enumdirection = EnumDirection.SOUTH;
|
||||
EnumDirection enumdirection1;
|
||||
|
||||
if (iblockdata3.getBlock() == this) {
|
||||
enumdirection1 = (EnumDirection) iblockdata3.get(BlockChest.FACING);
|
||||
} else {
|
||||
enumdirection1 = (EnumDirection) iblockdata4.get(BlockChest.FACING);
|
||||
}
|
||||
|
||||
if (enumdirection1 == EnumDirection.NORTH) {
|
||||
enumdirection = EnumDirection.NORTH;
|
||||
}
|
||||
|
||||
if ((flag || iblockdata5.b()) && !flag1 && !iblockdata6.b()) {
|
||||
enumdirection = EnumDirection.SOUTH;
|
||||
}
|
||||
|
||||
if ((flag1 || iblockdata6.b()) && !flag && !iblockdata5.b()) {
|
||||
enumdirection = EnumDirection.NORTH;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BlockPosition blockposition2 = iblockdata1.getBlock() == this ? blockposition.north() : blockposition.south();
|
||||
IBlockData iblockdata7 = world.getType(blockposition2.west());
|
||||
IBlockData iblockdata8 = world.getType(blockposition2.east());
|
||||
|
||||
enumdirection = EnumDirection.EAST;
|
||||
EnumDirection enumdirection2;
|
||||
|
||||
if (iblockdata1.getBlock() == this) {
|
||||
enumdirection2 = (EnumDirection) iblockdata1.get(BlockChest.FACING);
|
||||
} else {
|
||||
enumdirection2 = (EnumDirection) iblockdata2.get(BlockChest.FACING);
|
||||
}
|
||||
|
||||
if (enumdirection2 == EnumDirection.WEST) {
|
||||
enumdirection = EnumDirection.WEST;
|
||||
}
|
||||
|
||||
if ((iblockdata3.b() || iblockdata7.b()) && !iblockdata4.b() && !iblockdata8.b()) {
|
||||
enumdirection = EnumDirection.EAST;
|
||||
}
|
||||
|
||||
if ((iblockdata4.b() || iblockdata8.b()) && !iblockdata3.b() && !iblockdata7.b()) {
|
||||
enumdirection = EnumDirection.WEST;
|
||||
}
|
||||
}
|
||||
|
||||
iblockdata = iblockdata.set(BlockChest.FACING, enumdirection);
|
||||
world.setTypeAndData(blockposition, iblockdata, 3);
|
||||
return iblockdata;
|
||||
}
|
||||
}
|
||||
|
||||
public IBlockData f(World world, BlockPosition blockposition, IBlockData iblockdata) {
|
||||
EnumDirection enumdirection = null;
|
||||
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EnumDirection enumdirection1 = (EnumDirection) iterator.next();
|
||||
IBlockData iblockdata1 = world.getType(blockposition.shift(enumdirection1));
|
||||
|
||||
if (iblockdata1.getBlock() == this) {
|
||||
return iblockdata;
|
||||
}
|
||||
|
||||
if (iblockdata1.b()) {
|
||||
if (enumdirection != null) {
|
||||
enumdirection = null;
|
||||
break;
|
||||
}
|
||||
|
||||
enumdirection = enumdirection1;
|
||||
}
|
||||
}
|
||||
|
||||
if (enumdirection != null) {
|
||||
return iblockdata.set(BlockChest.FACING, enumdirection.opposite());
|
||||
} else {
|
||||
EnumDirection enumdirection2 = (EnumDirection) iblockdata.get(BlockChest.FACING);
|
||||
|
||||
if (world.getType(blockposition.shift(enumdirection2)).b()) {
|
||||
enumdirection2 = enumdirection2.opposite();
|
||||
}
|
||||
|
||||
if (world.getType(blockposition.shift(enumdirection2)).b()) {
|
||||
enumdirection2 = enumdirection2.e();
|
||||
}
|
||||
|
||||
if (world.getType(blockposition.shift(enumdirection2)).b()) {
|
||||
enumdirection2 = enumdirection2.opposite();
|
||||
}
|
||||
|
||||
return iblockdata.set(BlockChest.FACING, enumdirection2);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canPlace(World world, BlockPosition blockposition) {
|
||||
int i = 0;
|
||||
BlockPosition blockposition1 = blockposition.west();
|
||||
BlockPosition blockposition2 = blockposition.east();
|
||||
BlockPosition blockposition3 = blockposition.north();
|
||||
BlockPosition blockposition4 = blockposition.south();
|
||||
|
||||
if (world.getType(blockposition1).getBlock() == this) {
|
||||
if (this.d(world, blockposition1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (world.getType(blockposition2).getBlock() == this) {
|
||||
if (this.d(world, blockposition2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (world.getType(blockposition3).getBlock() == this) {
|
||||
if (this.d(world, blockposition3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (world.getType(blockposition4).getBlock() == this) {
|
||||
if (this.d(world, blockposition4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return i <= 1;
|
||||
}
|
||||
|
||||
private boolean d(World world, BlockPosition blockposition) {
|
||||
if (world.getType(blockposition).getBlock() != this) {
|
||||
return false;
|
||||
} else {
|
||||
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
|
||||
|
||||
EnumDirection enumdirection;
|
||||
|
||||
do {
|
||||
if (!iterator.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enumdirection = (EnumDirection) iterator.next();
|
||||
} while (world.getType(blockposition.shift(enumdirection)).getBlock() != this);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1) {
|
||||
super.a(iblockdata, world, blockposition, block, blockposition1);
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntityChest) {
|
||||
tileentity.invalidateBlockCache();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void remove(World world, BlockPosition blockposition, IBlockData iblockdata) {
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof IInventory) {
|
||||
InventoryUtils.dropInventory(world, blockposition, (IInventory) tileentity);
|
||||
world.updateAdjacentComparators(blockposition, this);
|
||||
}
|
||||
|
||||
super.remove(world, blockposition, iblockdata);
|
||||
}
|
||||
|
||||
public boolean interact(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman, EnumHand enumhand, EnumDirection enumdirection, float f, float f1, float f2) {
|
||||
if (world.isClientSide) {
|
||||
return true;
|
||||
} else {
|
||||
ITileInventory itileinventory = this.getInventory(world, blockposition);
|
||||
|
||||
if (itileinventory != null) {
|
||||
entityhuman.openContainer(itileinventory);
|
||||
if (this.g == BlockChest.Type.BASIC) {
|
||||
entityhuman.b(StatisticList.aa);
|
||||
} else if (this.g == BlockChest.Type.TRAP) {
|
||||
entityhuman.b(StatisticList.U);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ITileInventory getInventory(World world, BlockPosition blockposition) {
|
||||
return this.a(world, blockposition, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ITileInventory a(World world, BlockPosition blockposition, boolean flag) {
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (!(tileentity instanceof TileEntityChest)) {
|
||||
return null;
|
||||
} else {
|
||||
Object object = (TileEntityChest) tileentity;
|
||||
|
||||
if (!flag && this.e(world, blockposition)) {
|
||||
return null;
|
||||
} else {
|
||||
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EnumDirection enumdirection = (EnumDirection) iterator.next();
|
||||
BlockPosition blockposition1 = blockposition.shift(enumdirection);
|
||||
// Paper start - don't load chunks if the other side of the chest is in unloaded chunk
|
||||
final IBlockData type = world.getTypeIfLoaded(blockposition1); // Paper
|
||||
if (type == null) {
|
||||
continue;
|
||||
}
|
||||
Block block = type.getBlock();
|
||||
// Paper end
|
||||
|
||||
if (block == this) {
|
||||
if (!flag && this.e(world, blockposition1)) { // Paper - check for allowBlocked flag - MC-99321
|
||||
return null;
|
||||
}
|
||||
|
||||
TileEntity tileentity1 = world.getTileEntity(blockposition1);
|
||||
|
||||
if (tileentity1 instanceof TileEntityChest) {
|
||||
if (enumdirection != EnumDirection.WEST && enumdirection != EnumDirection.NORTH) {
|
||||
object = new InventoryLargeChest("container.chestDouble", (ITileInventory) object, (TileEntityChest) tileentity1);
|
||||
} else {
|
||||
object = new InventoryLargeChest("container.chestDouble", (TileEntityChest) tileentity1, (ITileInventory) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (ITileInventory) object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TileEntity a(World world, int i) {
|
||||
return new TileEntityChest();
|
||||
}
|
||||
|
||||
public boolean isPowerSource(IBlockData iblockdata) {
|
||||
return this.g == BlockChest.Type.TRAP;
|
||||
}
|
||||
|
||||
public int b(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) {
|
||||
if (!iblockdata.m()) {
|
||||
return 0;
|
||||
} else {
|
||||
int i = 0;
|
||||
TileEntity tileentity = iblockaccess.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntityChest) {
|
||||
i = ((TileEntityChest) tileentity).l;
|
||||
}
|
||||
|
||||
return MathHelper.clamp(i, 0, 15);
|
||||
}
|
||||
}
|
||||
|
||||
public int c(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) {
|
||||
return enumdirection == EnumDirection.UP ? iblockdata.a(iblockaccess, blockposition, enumdirection) : 0;
|
||||
}
|
||||
|
||||
private boolean e(World world, BlockPosition blockposition) {
|
||||
return this.i(world, blockposition) || this.j(world, blockposition);
|
||||
}
|
||||
|
||||
private boolean i(World world, BlockPosition blockposition) {
|
||||
return world.getType(blockposition.up()).l();
|
||||
}
|
||||
|
||||
private boolean j(World world, BlockPosition blockposition) {
|
||||
// Paper start - Option ti dsiable chest cat detection
|
||||
if (world.paperConfig.disableChestCatDetection) {
|
||||
return false;
|
||||
}
|
||||
// Paper end
|
||||
Iterator iterator = world.a(EntityOcelot.class, new AxisAlignedBB((double) blockposition.getX(), (double) (blockposition.getY() + 1), (double) blockposition.getZ(), (double) (blockposition.getX() + 1), (double) (blockposition.getY() + 2), (double) (blockposition.getZ() + 1))).iterator();
|
||||
|
||||
EntityOcelot entityocelot;
|
||||
|
||||
do {
|
||||
if (!iterator.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Entity entity = (Entity) iterator.next();
|
||||
|
||||
entityocelot = (EntityOcelot) entity;
|
||||
} while (!entityocelot.isSitting());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isComplexRedstone(IBlockData iblockdata) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int c(IBlockData iblockdata, World world, BlockPosition blockposition) {
|
||||
return Container.b((IInventory) this.getInventory(world, blockposition));
|
||||
}
|
||||
|
||||
public IBlockData fromLegacyData(int i) {
|
||||
EnumDirection enumdirection = EnumDirection.fromType1(i);
|
||||
|
||||
if (enumdirection.k() == EnumDirection.EnumAxis.Y) {
|
||||
enumdirection = EnumDirection.NORTH;
|
||||
}
|
||||
|
||||
return this.getBlockData().set(BlockChest.FACING, enumdirection);
|
||||
}
|
||||
|
||||
public int toLegacyData(IBlockData iblockdata) {
|
||||
return ((EnumDirection) iblockdata.get(BlockChest.FACING)).a();
|
||||
}
|
||||
|
||||
public IBlockData a(IBlockData iblockdata, EnumBlockRotation enumblockrotation) {
|
||||
return iblockdata.set(BlockChest.FACING, enumblockrotation.a((EnumDirection) iblockdata.get(BlockChest.FACING)));
|
||||
}
|
||||
|
||||
public IBlockData a(IBlockData iblockdata, EnumBlockMirror enumblockmirror) {
|
||||
return iblockdata.a(enumblockmirror.a((EnumDirection) iblockdata.get(BlockChest.FACING)));
|
||||
}
|
||||
|
||||
protected BlockStateList getStateList() {
|
||||
return new BlockStateList(this, new IBlockState[] { BlockChest.FACING});
|
||||
}
|
||||
|
||||
public EnumBlockFaceShape a(IBlockAccess iblockaccess, IBlockData iblockdata, BlockPosition blockposition, EnumDirection enumdirection) {
|
||||
return EnumBlockFaceShape.UNDEFINED;
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
|
||||
BASIC, TRAP;
|
||||
|
||||
private Type() {}
|
||||
}
|
||||
}
|
||||
118
sources/src/main/java/net/minecraft/server/BlockStationary.java
Normal file
118
sources/src/main/java/net/minecraft/server/BlockStationary.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
|
||||
|
||||
public class BlockStationary extends BlockFluids {
|
||||
|
||||
protected BlockStationary(Material material) {
|
||||
super(material);
|
||||
this.a(false);
|
||||
if (material == Material.LAVA) {
|
||||
this.a(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1) {
|
||||
if (!this.e(world, blockposition, iblockdata)) {
|
||||
this.f(world, blockposition, iblockdata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void f(World world, BlockPosition blockposition, IBlockData iblockdata) {
|
||||
BlockFlowing blockflowing = a(this.material);
|
||||
|
||||
world.setTypeAndData(blockposition, blockflowing.getBlockData().set(BlockStationary.LEVEL, iblockdata.get(BlockStationary.LEVEL)), 2);
|
||||
world.a(blockposition, (Block) blockflowing, this.a(world));
|
||||
}
|
||||
|
||||
public void b(World world, BlockPosition blockposition, IBlockData iblockdata, Random random) {
|
||||
if (this.material == Material.LAVA) {
|
||||
if (world.getGameRules().getBoolean("doFireTick")) {
|
||||
int i = random.nextInt(3);
|
||||
|
||||
if (i > 0) {
|
||||
BlockPosition blockposition1 = blockposition;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
blockposition1 = blockposition1.a(random.nextInt(3) - 1, 1, random.nextInt(3) - 1);
|
||||
if (blockposition1.getY() >= 0 && blockposition1.getY() < 256 && !world.isLoaded(blockposition1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = world.getType(blockposition1).getBlock();
|
||||
|
||||
if (block.material == Material.AIR) {
|
||||
if (this.c(world, blockposition1)) {
|
||||
// CraftBukkit start - Prevent lava putting something on fire
|
||||
if (world.getType(blockposition1) != Blocks.FIRE) {
|
||||
if (CraftEventFactory.callBlockIgniteEvent(world, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ(), blockposition.getX(), blockposition.getY(), blockposition.getZ()).isCancelled()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
world.setTypeUpdate(blockposition1, Blocks.FIRE.getBlockData());
|
||||
return;
|
||||
}
|
||||
} else if (block.material.isSolid()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
BlockPosition blockposition2 = blockposition.a(random.nextInt(3) - 1, 0, random.nextInt(3) - 1);
|
||||
|
||||
if (blockposition2.getY() >= 0 && blockposition2.getY() < 256 && !world.isLoaded(blockposition2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (world.isEmpty(blockposition2.up()) && this.d(world, blockposition2)) {
|
||||
// CraftBukkit start - Prevent lava putting something on fire
|
||||
BlockPosition up = blockposition2.up();
|
||||
if (world.getType(up) != Blocks.FIRE) {
|
||||
if (CraftEventFactory.callBlockIgniteEvent(world, up.getX(), up.getY(), up.getZ(), blockposition.getX(), blockposition.getY(), blockposition.getZ()).isCancelled()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
world.setTypeUpdate(blockposition2.up(), Blocks.FIRE.getBlockData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean c(World world, BlockPosition blockposition) {
|
||||
EnumDirection[] aenumdirection = EnumDirection.values();
|
||||
int i = aenumdirection.length;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
EnumDirection enumdirection = aenumdirection[j];
|
||||
|
||||
if (this.d(world, blockposition.shift(enumdirection))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean d(World world, BlockPosition blockposition) {
|
||||
// Dionysus start - improve fire spread checks
|
||||
if (blockposition.getY() >= 0 && blockposition.getY() < 256) {
|
||||
IBlockData blockData = world.getTypeIfLoaded(blockposition);
|
||||
|
||||
if (blockData != null) {
|
||||
return blockData.getMaterial().isBurnable();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// Dionysus end
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
// Paper start
|
||||
import com.destroystokyo.paper.PaperWorldConfig.DuplicateUUIDMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
// Paper end
|
||||
import com.destroystokyo.paper.exception.ServerInternalException;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Queues;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -26,16 +32,46 @@ public class Chunk {
|
||||
private final byte[] g;
|
||||
private final int[] h;
|
||||
private final boolean[] i;
|
||||
private boolean j;
|
||||
private boolean j; public boolean isLoaded() { return j; } // Paper - OBFHELPER
|
||||
public final World world;
|
||||
public final int[] heightMap;
|
||||
public Long scheduledForUnload; // Paper - delay chunk unloads
|
||||
private static final Logger logger = LogManager.getLogger(); // Paper
|
||||
public final int locX;
|
||||
public final int locZ;
|
||||
private boolean m;
|
||||
public final Map<BlockPosition, TileEntity> tileEntities;
|
||||
public final List<Entity>[] entitySlices; // Spigot
|
||||
final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Paper
|
||||
// Paper start
|
||||
public final co.aikar.util.Counter<String> entityCounts = new co.aikar.util.Counter<>();
|
||||
public final co.aikar.util.Counter<String> tileEntityCounts = new co.aikar.util.Counter<>();
|
||||
private class TileEntityHashMap extends java.util.HashMap<BlockPosition, TileEntity> {
|
||||
@Override
|
||||
public TileEntity put(BlockPosition key, TileEntity value) {
|
||||
TileEntity replaced = super.put(key, value);
|
||||
if (replaced != null) {
|
||||
replaced.setCurrentChunk(null);
|
||||
tileEntityCounts.decrement(replaced.getMinecraftKeyString());
|
||||
}
|
||||
if (value != null) {
|
||||
value.setCurrentChunk(Chunk.this);
|
||||
tileEntityCounts.increment(value.getMinecraftKeyString());
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity remove(Object key) {
|
||||
TileEntity removed = super.remove(key);
|
||||
if (removed != null) {
|
||||
removed.setCurrentChunk(null);
|
||||
tileEntityCounts.decrement(removed.getMinecraftKeyString());
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this);
|
||||
// Paper end
|
||||
private boolean done;
|
||||
private boolean lit;
|
||||
private boolean r; private boolean isTicked() { return r; }; // Paper - OBFHELPER
|
||||
@@ -90,10 +126,10 @@ public class Chunk {
|
||||
this.g = new byte[256];
|
||||
this.h = new int[256];
|
||||
this.i = new boolean[256];
|
||||
this.tileEntities = Maps.newHashMap();
|
||||
this.tileEntities = new TileEntityHashMap(); // Paper
|
||||
this.x = 4096;
|
||||
this.y = Queues.newConcurrentLinkedQueue();
|
||||
this.entitySlices = (new List[16]); // Spigot
|
||||
this.entitySlices = (List[]) (new List[16]); // Spigot
|
||||
this.world = world;
|
||||
this.locX = i;
|
||||
this.locZ = j;
|
||||
@@ -238,14 +274,7 @@ public class Chunk {
|
||||
|
||||
private void h(boolean flag) {
|
||||
this.world.methodProfiler.a("recheckGaps");
|
||||
if (this.world.areChunksLoaded(new BlockPosition(this.locX * 16 + 8, 0, this.locZ * 16 + 8), 16)) {
|
||||
this.runOrQueueLightUpdate(() -> recheckGaps(flag)); // Paper - Queue light update
|
||||
}
|
||||
}
|
||||
|
||||
private void recheckGaps(boolean flag) {
|
||||
if (true) {
|
||||
// Paper end
|
||||
if (this.areNeighborsLoaded(1)) { // Paper
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (this.i[i + j * 16]) {
|
||||
@@ -296,7 +325,7 @@ public class Chunk {
|
||||
}
|
||||
|
||||
private void a(int i, int j, int k, int l) {
|
||||
if (l > k && this.world.areChunksLoaded(new BlockPosition(i, 0, j), 16)) {
|
||||
if (l > k && this.areNeighborsLoaded(1)) { // Paper
|
||||
for (int i1 = k; i1 < l; ++i1) {
|
||||
this.world.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
|
||||
}
|
||||
@@ -459,7 +488,6 @@ public class Chunk {
|
||||
return CrashReportSystemDetails.a(i, j, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
@@ -629,6 +657,7 @@ public class Chunk {
|
||||
if (i != this.locX || j != this.locZ) {
|
||||
Chunk.e.warn("Wrong location! ({}, {}) should be ({}, {}), {}", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(this.locX), Integer.valueOf(this.locZ), entity);
|
||||
entity.die();
|
||||
return; // Paper
|
||||
}
|
||||
|
||||
int k = MathHelper.floor(entity.locY / 16.0D);
|
||||
@@ -645,8 +674,30 @@ public class Chunk {
|
||||
entity.ab = this.locX;
|
||||
entity.ac = k;
|
||||
entity.ad = this.locZ;
|
||||
this.entitySlices[k].add(entity);
|
||||
// Paper start - update count
|
||||
|
||||
// Paper start
|
||||
List<Entity> entitySlice = this.entitySlices[k];
|
||||
boolean inThis = entitySlice.contains(entity);
|
||||
List<Entity> currentSlice = entity.entitySlice;
|
||||
if (inThis || (currentSlice != null && currentSlice.contains(entity))) {
|
||||
if (currentSlice == entitySlice || inThis) {
|
||||
return;
|
||||
} else {
|
||||
Chunk chunk = entity.getCurrentChunk();
|
||||
if (chunk != null) {
|
||||
chunk.removeEntity(entity);
|
||||
} else {
|
||||
removeEntity(entity);
|
||||
}
|
||||
currentSlice.remove(entity); // Just incase the above did not remove from this target slice
|
||||
}
|
||||
}
|
||||
entity.entitySlice = entitySlice;
|
||||
entitySlice.add(entity);
|
||||
|
||||
this.markDirty();
|
||||
entity.setCurrentChunk(this);
|
||||
entityCounts.increment(entity.getMinecraftKeyString());
|
||||
if (entity instanceof EntityItem) {
|
||||
itemCounts[k]++;
|
||||
} else if (entity instanceof IInventory) {
|
||||
@@ -671,6 +722,7 @@ public class Chunk {
|
||||
// Spigot end
|
||||
}
|
||||
|
||||
public void removeEntity(Entity entity) { b(entity); } // Paper - OBFHELPER
|
||||
public void b(Entity entity) {
|
||||
this.a(entity, entity.ac);
|
||||
}
|
||||
@@ -684,8 +736,14 @@ public class Chunk {
|
||||
i = this.entitySlices.length - 1;
|
||||
}
|
||||
|
||||
if (!this.entitySlices[i].remove(entity)) { return; } // Paper
|
||||
// Paper start - update counts
|
||||
// Paper start
|
||||
if (entity.entitySlice == null || !entity.entitySlice.contains(entity) || entitySlices[i] == entity.entitySlice) {
|
||||
entity.entitySlice = null;
|
||||
}
|
||||
if (!this.entitySlices[i].remove(entity)) { return; }
|
||||
this.markDirty();
|
||||
entity.setCurrentChunk(null);
|
||||
entityCounts.decrement(entity.getMinecraftKeyString());
|
||||
if (entity instanceof EntityItem) {
|
||||
itemCounts[i]--;
|
||||
} else if (entity instanceof IInventory) {
|
||||
@@ -735,7 +793,7 @@ public class Chunk {
|
||||
tileentity = world.capturedTileEntities.get(blockposition);
|
||||
}
|
||||
if (tileentity == null) {
|
||||
tileentity = this.tileEntities.get(blockposition);
|
||||
tileentity = (TileEntity) this.tileEntities.get(blockposition);
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
@@ -767,7 +825,7 @@ public class Chunk {
|
||||
tileentity.setPosition(blockposition);
|
||||
if (this.getBlockData(blockposition).getBlock() instanceof ITileEntity) {
|
||||
if (this.tileEntities.containsKey(blockposition)) {
|
||||
this.tileEntities.get(blockposition).z();
|
||||
((TileEntity) this.tileEntities.get(blockposition)).z();
|
||||
}
|
||||
|
||||
tileentity.A();
|
||||
@@ -789,6 +847,7 @@ public class Chunk {
|
||||
|
||||
if (this.world.paperConfig.removeCorruptTEs) {
|
||||
this.removeTileEntity(tileentity.getPosition());
|
||||
this.markDirty();
|
||||
org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity");
|
||||
}
|
||||
// Paper end
|
||||
@@ -799,7 +858,7 @@ public class Chunk {
|
||||
public void removeTileEntity(BlockPosition blockposition) { this.d(blockposition); } // Paper - OBFHELPER
|
||||
public void d(BlockPosition blockposition) {
|
||||
if (this.j) {
|
||||
TileEntity tileentity = this.tileEntities.remove(blockposition);
|
||||
TileEntity tileentity = (TileEntity) this.tileEntities.remove(blockposition);
|
||||
|
||||
if (tileentity != null) {
|
||||
tileentity.z();
|
||||
@@ -813,12 +872,59 @@ public class Chunk {
|
||||
this.world.b(this.tileEntities.values());
|
||||
List[] aentityslice = this.entitySlices; // Spigot
|
||||
int i = aentityslice.length;
|
||||
List<Entity> toAdd = new java.util.ArrayList<>(32); // Paper
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
List entityslice = aentityslice[j]; // Spigot
|
||||
// Paper start
|
||||
DuplicateUUIDMode mode = world.paperConfig.duplicateUUIDMode;
|
||||
if (mode == DuplicateUUIDMode.WARN || mode == DuplicateUUIDMode.DELETE || mode == DuplicateUUIDMode.SAFE_REGEN) {
|
||||
Map<UUID, Entity> thisChunk = new HashMap<>();
|
||||
for (Iterator<Entity> iterator = ((List<Entity>) entityslice).iterator(); iterator.hasNext(); ) {
|
||||
Entity entity = iterator.next();
|
||||
if (entity.dead || entity.valid) continue;
|
||||
Entity other = ((WorldServer) world).entitiesByUUID.get(entity.uniqueID);
|
||||
if (other == null || other.dead || world.getEntityUnloadQueue().contains(other)) {
|
||||
other = thisChunk.get(entity.uniqueID);
|
||||
}
|
||||
|
||||
this.world.a(entityslice);
|
||||
if (mode == DuplicateUUIDMode.SAFE_REGEN && other != null && !other.dead &&
|
||||
!world.getEntityUnloadQueue().contains(other)
|
||||
&& java.util.Objects.equals(other.getSaveID(), entity.getSaveID())
|
||||
&& entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < world.paperConfig.duplicateUUIDDeleteRange
|
||||
) {
|
||||
logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
|
||||
entity.die();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
if (other != null && !other.dead) {
|
||||
switch (mode) {
|
||||
case SAFE_REGEN: {
|
||||
entity.setUUID(UUID.randomUUID());
|
||||
logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
|
||||
break;
|
||||
}
|
||||
case DELETE: {
|
||||
logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
|
||||
entity.die();
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
thisChunk.put(entity.uniqueID, entity);
|
||||
}
|
||||
}
|
||||
|
||||
//this.world.a((Collection) entityslice); // Move down, add all entities at same time
|
||||
toAdd.addAll(entityslice);
|
||||
// Paper end
|
||||
}
|
||||
this.world.addChunkEntities(toAdd); // Paper - add all at same time to avoid entities adding to world modifying slice state
|
||||
|
||||
}
|
||||
|
||||
@@ -831,11 +937,11 @@ public class Chunk {
|
||||
// Spigot Start
|
||||
if ( tileentity instanceof IInventory )
|
||||
{
|
||||
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList(( (IInventory) tileentity ).getViewers() ) )
|
||||
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList((List<org.bukkit.entity.HumanEntity>) ( (IInventory) tileentity ).getViewers() ) )
|
||||
{
|
||||
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
|
||||
{
|
||||
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory();
|
||||
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -856,15 +962,17 @@ public class Chunk {
|
||||
// Spigot Start
|
||||
if ( entity instanceof IInventory )
|
||||
{
|
||||
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList( ( (IInventory) entity ).getViewers() ) )
|
||||
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList( (List<org.bukkit.entity.HumanEntity>) ( (IInventory) entity ).getViewers() ) )
|
||||
{
|
||||
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
|
||||
{
|
||||
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory();
|
||||
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper
|
||||
}
|
||||
}
|
||||
}
|
||||
// Spigot End
|
||||
entity.setCurrentChunk(null); // Paper
|
||||
entity.entitySlice = null; // Paper
|
||||
|
||||
// Do not pass along players, as doing so can get them stuck outside of time.
|
||||
// (which for example disables inventory icon updates and prevents block breaking)
|
||||
@@ -892,7 +1000,6 @@ public class Chunk {
|
||||
|
||||
for (int k = i; k <= j; ++k) {
|
||||
if (!this.entitySlices[k].isEmpty()) {
|
||||
Iterator iterator = this.entitySlices[k].iterator();
|
||||
|
||||
// Paper start - Don't search for inventories if we have none, and that is all we want
|
||||
/*
|
||||
@@ -903,6 +1010,7 @@ public class Chunk {
|
||||
*/
|
||||
if (predicate == IEntitySelector.c && inventoryEntityCounts[k] <= 0) continue;
|
||||
// Paper end
|
||||
Iterator iterator = this.entitySlices[k].iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity1 = (Entity) iterator.next();
|
||||
|
||||
@@ -975,7 +1083,7 @@ public class Chunk {
|
||||
}
|
||||
|
||||
public Random a(long i) {
|
||||
return new Random(this.world.getSeed() + this.locX * this.locX * 4987142 + this.locX * 5947611 + this.locZ * this.locZ * 4392871L + this.locZ * 389711 ^ i);
|
||||
return new Random(this.world.getSeed() + (long) (this.locX * this.locX * 4987142) + (long) (this.locX * 5947611) + (long) (this.locZ * this.locZ) * 4392871L + (long) (this.locZ * 389711) ^ i);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
@@ -1055,7 +1163,7 @@ public class Chunk {
|
||||
random.setSeed(world.getSeed());
|
||||
long xRand = random.nextLong() / 2L * 2L + 1L;
|
||||
long zRand = random.nextLong() / 2L * 2L + 1L;
|
||||
random.setSeed(locX * xRand + locZ * zRand ^ world.getSeed());
|
||||
random.setSeed((long) locX * xRand + (long) locZ * zRand ^ world.getSeed());
|
||||
|
||||
org.bukkit.World world = this.world.getWorld();
|
||||
if (world != null) {
|
||||
@@ -1111,12 +1219,12 @@ public class Chunk {
|
||||
}
|
||||
|
||||
this.r = true;
|
||||
if (!this.lit && this.done && this.world.spigotConfig.randomLightUpdates) { // Spigot - also use random light updates setting to determine if we should relight
|
||||
if (!this.lit && this.done) {
|
||||
this.o();
|
||||
}
|
||||
|
||||
while (!this.y.isEmpty()) {
|
||||
BlockPosition blockposition = this.y.poll();
|
||||
BlockPosition blockposition = (BlockPosition) this.y.poll();
|
||||
|
||||
if (this.a(blockposition, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockposition).getBlock().isTileEntity()) {
|
||||
TileEntity tileentity = this.g(blockposition);
|
||||
@@ -1137,7 +1245,7 @@ public class Chunk {
|
||||
* For now at least we will simply send all chunks, in accordance with pre 1.7 behaviour.
|
||||
*/
|
||||
// Paper Start
|
||||
// if randomLightUpdates are enabled, we should always return true, otherwise chunks may never send
|
||||
// if randomLightUpdates are disabled, we should always return true, otherwise chunks may never send
|
||||
// to the client due to not being lit, otherwise retain standard behavior and only send properly lit chunks.
|
||||
return !this.world.spigotConfig.randomLightUpdates || (this.isTicked() && this.done && this.lit);
|
||||
// Paper End
|
||||
@@ -1298,7 +1406,7 @@ public class Chunk {
|
||||
this.h(false);
|
||||
}
|
||||
|
||||
public void a(EnumDirection enumdirection) { // Akarin - private -> public - PAIL: checkLightSide
|
||||
private void a(EnumDirection enumdirection) {
|
||||
if (this.done) {
|
||||
int i;
|
||||
|
||||
@@ -1333,7 +1441,7 @@ public class Chunk {
|
||||
|
||||
for (l = k + 16 - 1; l > this.world.getSeaLevel() || l > 0 && !flag1; --l) {
|
||||
blockposition_mutableblockposition.c(blockposition_mutableblockposition.getX(), l, blockposition_mutableblockposition.getZ());
|
||||
int i1 = this.b(blockposition_mutableblockposition);
|
||||
int i1 = this.b((BlockPosition) blockposition_mutableblockposition);
|
||||
|
||||
if (i1 == 255 && blockposition_mutableblockposition.getY() < this.world.getSeaLevel()) {
|
||||
flag1 = true;
|
||||
@@ -1440,4 +1548,14 @@ public class Chunk {
|
||||
|
||||
private EnumTileEntityState() {}
|
||||
}
|
||||
// FlamePaper start - Hopper item lookup optimization
|
||||
public int getItemCount(BlockPosition blockPosition) {
|
||||
int k = MathHelper.floor(blockPosition.getY() / 16.0D);
|
||||
|
||||
k = Math.max(0, k);
|
||||
k = Math.min(this.entitySlices.length - 1, k);
|
||||
|
||||
return itemCounts[k];
|
||||
}
|
||||
// FlamePaper end - Hopper item lookup optimization
|
||||
}
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.destroystokyo.paper.PaperConfig;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongArraySet;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import com.destroystokyo.paper.exception.ServerInternalException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
// CraftBukkit start
|
||||
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
// CraftBukkit end
|
||||
|
||||
public class ChunkProviderServer implements IChunkProvider {
|
||||
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
public final LongArraySet unloadQueue = new LongArraySet(512); // Dionysus
|
||||
public final ChunkGenerator chunkGenerator;
|
||||
private final IChunkLoader chunkLoader;
|
||||
// Paper start - chunk save stats
|
||||
private long lastQueuedSaves = 0L; // Paper
|
||||
private long lastProcessedSaves = 0L; // Paper
|
||||
private long lastSaveStatPrinted = System.currentTimeMillis();
|
||||
// Paper end
|
||||
// Paper start
|
||||
protected Chunk lastChunkByPos = null;
|
||||
public Long2ObjectOpenHashMap<Chunk> chunks = new Long2ObjectOpenHashMap<Chunk>(8192) {
|
||||
|
||||
@Override
|
||||
public Chunk get(long key) {
|
||||
if (lastChunkByPos != null && key == lastChunkByPos.chunkKey) {
|
||||
return lastChunkByPos;
|
||||
}
|
||||
return lastChunkByPos = super.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk remove(long key) {
|
||||
if (lastChunkByPos != null && key == lastChunkByPos.chunkKey) {
|
||||
lastChunkByPos = null;
|
||||
}
|
||||
return super.remove(key);
|
||||
}
|
||||
}; // CraftBukkit
|
||||
// Paper end
|
||||
public final WorldServer world;
|
||||
|
||||
public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator chunkgenerator) {
|
||||
this.world = worldserver;
|
||||
this.chunkLoader = ichunkloader;
|
||||
this.chunkGenerator = chunkgenerator;
|
||||
}
|
||||
|
||||
public Collection<Chunk> a() {
|
||||
return this.chunks.values();
|
||||
}
|
||||
|
||||
public void unload(Chunk chunk) {
|
||||
if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) {
|
||||
this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)));
|
||||
chunk.d = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void b() {
|
||||
ObjectIterator objectiterator = this.chunks.values().iterator();
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
Chunk chunk = (Chunk) objectiterator.next();
|
||||
|
||||
this.unload(chunk);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Paper start
|
||||
public boolean isChunkGenerated(int x, int z) {
|
||||
return this.chunks.containsKey(ChunkCoordIntPair.asLong(x, z)) || this.chunkLoader.chunkExists(x, z);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@Nullable
|
||||
public Chunk getLoadedChunkAt(int i, int j) {
|
||||
long k = ChunkCoordIntPair.a(i, j);
|
||||
Chunk chunk = (Chunk) this.chunks.get(k);
|
||||
|
||||
if (chunk != null) {
|
||||
chunk.d = false;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Chunk getOrLoadChunkAt(int i, int j) {
|
||||
Chunk chunk = this.getLoadedChunkAt(i, j);
|
||||
|
||||
if (chunk == null) {
|
||||
// CraftBukkit start
|
||||
ChunkRegionLoader loader = null;
|
||||
|
||||
if (this.chunkLoader instanceof ChunkRegionLoader) {
|
||||
loader = (ChunkRegionLoader) this.chunkLoader;
|
||||
}
|
||||
if (loader != null && loader.chunkExists(i, j)) {
|
||||
chunk = ChunkIOExecutor.syncChunkLoad(world, loader, this, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Chunk originalGetOrLoadChunkAt(int i, int j) {
|
||||
// CraftBukkit end
|
||||
Chunk chunk = this.getLoadedChunkAt(i, j);
|
||||
|
||||
if (chunk == null) {
|
||||
chunk = this.loadChunk(i, j);
|
||||
if (chunk != null) {
|
||||
this.chunks.put(ChunkCoordIntPair.a(i, j), chunk);
|
||||
chunk.addEntities();
|
||||
chunk.loadNearby(this, this.chunkGenerator, false); // CraftBukkit
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
public Chunk getChunkIfLoaded(int x, int z) {
|
||||
return chunks.get(ChunkCoordIntPair.a(x, z));
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
public Chunk getChunkAt(int i, int j) {
|
||||
return getChunkAt(i, j, null);
|
||||
}
|
||||
|
||||
public Chunk getChunkAt(int i, int j, Runnable runnable) {
|
||||
return getChunkAt(i, j, runnable, true);
|
||||
}
|
||||
|
||||
public Chunk getChunkAt(int i, int j, Runnable runnable, boolean generate) {
|
||||
Chunk chunk = world.paperConfig.allowPermaChunkLoaders ? getLoadedChunkAt(i, j) : getChunkIfLoaded(i, j); // Paper - Configurable perma chunk loaders
|
||||
ChunkRegionLoader loader = null;
|
||||
|
||||
if (this.chunkLoader instanceof ChunkRegionLoader) {
|
||||
loader = (ChunkRegionLoader) this.chunkLoader;
|
||||
|
||||
}
|
||||
// We can only use the queue for already generated chunks
|
||||
if (chunk == null && loader != null && loader.chunkExists(i, j)) {
|
||||
if (runnable != null) {
|
||||
ChunkIOExecutor.queueChunkLoad(world, loader, this, i, j, runnable);
|
||||
return null;
|
||||
} else {
|
||||
chunk = ChunkIOExecutor.syncChunkLoad(world, loader, this, i, j);
|
||||
|
||||
// Paper start - If there was an issue loading the chunk from region, stage1 will fail and stage2 will load it sync
|
||||
// all we need to do is fetch an instance
|
||||
if (chunk == null) {
|
||||
chunk = getChunkIfLoaded(i, j);
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
} else if (chunk == null && generate) {
|
||||
chunk = originalGetChunkAt(i, j);
|
||||
}
|
||||
|
||||
// If we didn't load the chunk async and have a callback run it now
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public Chunk originalGetChunkAt(int i, int j) {
|
||||
Chunk chunk = this.originalGetOrLoadChunkAt(i, j);
|
||||
// CraftBukkit end
|
||||
|
||||
if (chunk == null) {
|
||||
world.timings.syncChunkLoadTimer.startTiming(); // Spigot
|
||||
long k = ChunkCoordIntPair.a(i, j);
|
||||
|
||||
try {
|
||||
chunk = this.chunkGenerator.getOrCreateChunk(i, j);
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk");
|
||||
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated");
|
||||
|
||||
crashreportsystemdetails.a("Location", (Object) String.format("%d,%d", new Object[] { Integer.valueOf(i), Integer.valueOf(j)}));
|
||||
crashreportsystemdetails.a("Position hash", (Object) Long.valueOf(k));
|
||||
crashreportsystemdetails.a("Generator", (Object) this.chunkGenerator);
|
||||
throw new ReportedException(crashreport);
|
||||
}
|
||||
|
||||
this.chunks.put(k, chunk);
|
||||
chunk.addEntities();
|
||||
chunk.loadNearby(this, this.chunkGenerator, true); // CraftBukkit
|
||||
world.timings.syncChunkLoadTimer.stopTiming(); // Spigot
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Chunk loadChunk(int i, int j) {
|
||||
try {
|
||||
Chunk chunk = this.chunkLoader.a(this.world, i, j);
|
||||
|
||||
if (chunk != null) {
|
||||
chunk.setLastSaved(this.world.getTime());
|
||||
this.chunkGenerator.recreateStructures(chunk, i, j);
|
||||
}
|
||||
|
||||
return chunk;
|
||||
} catch (Exception exception) {
|
||||
// Paper start
|
||||
String msg = "Couldn\'t load chunk";
|
||||
ChunkProviderServer.a.error(msg, exception);
|
||||
ServerInternalException.reportInternalException(exception);
|
||||
// Paper end
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveChunkNOP(Chunk chunk) {
|
||||
try {
|
||||
// this.chunkLoader.b(this.world, chunk); // Spigot
|
||||
} catch (Exception exception) {
|
||||
// Paper start
|
||||
String msg = "Couldn\'t save entities";
|
||||
ChunkProviderServer.a.error(msg, exception);
|
||||
ServerInternalException.reportInternalException(exception);
|
||||
// Paper end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void saveChunk(Chunk chunk, boolean unloaded) { // Spigot
|
||||
try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) {
|
||||
chunk.setLastSaved(this.world.getTime());
|
||||
this.chunkLoader.saveChunk(this.world, chunk, unloaded); // Spigot
|
||||
} catch (IOException ioexception) {
|
||||
// Paper start
|
||||
String msg = "Couldn\'t save chunk";
|
||||
ChunkProviderServer.a.error(msg, ioexception);
|
||||
ServerInternalException.reportInternalException(ioexception);
|
||||
} catch (ExceptionWorldConflict exceptionworldconflict) {
|
||||
String msg = "Couldn\'t save chunk; already in use by another instance of Minecraft?";
|
||||
ChunkProviderServer.a.error(msg, exceptionworldconflict);
|
||||
ServerInternalException.reportInternalException(exceptionworldconflict);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean a(boolean flag) {
|
||||
int i = 0;
|
||||
|
||||
// CraftBukkit start
|
||||
// Paper start
|
||||
final ChunkRegionLoader chunkLoader = (ChunkRegionLoader) world.getChunkProviderServer().chunkLoader;
|
||||
final int queueSize = chunkLoader.getQueueSize();
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
final long timeSince = (now - lastSaveStatPrinted) / 1000;
|
||||
final Integer printRateSecs = Integer.getInteger("printSaveStats");
|
||||
if (printRateSecs != null && timeSince >= printRateSecs) {
|
||||
final String timeStr = "/" + timeSince +"s";
|
||||
final long queuedSaves = chunkLoader.getQueuedSaves();
|
||||
long queuedDiff = queuedSaves - lastQueuedSaves;
|
||||
lastQueuedSaves = queuedSaves;
|
||||
|
||||
final long processedSaves = chunkLoader.getProcessedSaves();
|
||||
long processedDiff = processedSaves - lastProcessedSaves;
|
||||
lastProcessedSaves = processedSaves;
|
||||
|
||||
lastSaveStatPrinted = now;
|
||||
if (processedDiff > 0 || queueSize > 0 || queuedDiff > 0) {
|
||||
System.out.println("[Chunk Save Stats] " + world.worldData.getName() +
|
||||
" - Current: " + queueSize +
|
||||
" - Queued: " + queuedDiff + timeStr +
|
||||
" - Processed: " +processedDiff + timeStr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (queueSize > world.paperConfig.queueSizeAutoSaveThreshold){
|
||||
return false;
|
||||
}
|
||||
final int autoSaveLimit = world.paperConfig.maxAutoSaveChunksPerTick;
|
||||
// Paper end
|
||||
Iterator iterator = this.chunks.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Chunk chunk = (Chunk) iterator.next();
|
||||
// CraftBukkit end
|
||||
|
||||
if (flag) {
|
||||
this.saveChunkNOP(chunk);
|
||||
}
|
||||
|
||||
if (chunk.a(flag)) {
|
||||
this.saveChunk(chunk, false); // Spigot
|
||||
chunk.f(false);
|
||||
++i;
|
||||
if (!flag && i >= autoSaveLimit) { // Spigot - // Paper - Incremental Auto Save - cap max per tick
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void c() {
|
||||
this.chunkLoader.c();
|
||||
}
|
||||
|
||||
private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96;
|
||||
|
||||
public boolean unloadChunks() {
|
||||
if (!this.world.savingDisabled) {
|
||||
if (!this.unloadQueue.isEmpty()) {
|
||||
// Spigot start
|
||||
org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant;
|
||||
activityAccountant.startActivity(0.5);
|
||||
int targetSize = Math.min(this.unloadQueue.size() - 100, (int) (this.unloadQueue.size() * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
||||
// Spigot end
|
||||
|
||||
LongIterator iterator = this.unloadQueue.iterator();
|
||||
|
||||
while (iterator.hasNext()) { // Spigot
|
||||
Long chunkKey = iterator.nextLong();
|
||||
iterator.remove(); // Spigot
|
||||
Chunk chunk = (Chunk) this.chunks.get(chunkKey);
|
||||
|
||||
if (chunk != null && chunk.d) {
|
||||
// CraftBukkit start - move unload logic to own method
|
||||
chunk.setShouldUnload(false); // Paper
|
||||
if (!unloadChunk(chunk, true)) {
|
||||
continue;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
// Spigot start
|
||||
if (this.unloadQueue.size() <= targetSize && activityAccountant.activityTimeIsExhausted()) {
|
||||
break;
|
||||
}
|
||||
// Spigot end
|
||||
}
|
||||
}
|
||||
|
||||
activityAccountant.endActivity(); // Spigot
|
||||
}
|
||||
// Paper start - delayed chunk unloads
|
||||
long now = System.currentTimeMillis();
|
||||
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
||||
if (unloadAfter > 0) {
|
||||
//noinspection Convert2streamapi
|
||||
for (Chunk chunk : chunks.values()) {
|
||||
if (chunk.scheduledForUnload != null && now - chunk.scheduledForUnload > unloadAfter) {
|
||||
chunk.scheduledForUnload = null;
|
||||
unload(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
|
||||
this.chunkLoader.b();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
public boolean unloadChunk(Chunk chunk, boolean save) {
|
||||
ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk, save);
|
||||
this.world.getServer().getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
save = event.isSaveChunk();
|
||||
chunk.lightingQueue.processUnload(); // Paper
|
||||
|
||||
// Update neighbor counts
|
||||
for (int x = -2; x < 3; x++) {
|
||||
for (int z = -2; z < 3; z++) {
|
||||
if (x == 0 && z == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Chunk neighbor = this.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
|
||||
if (neighbor != null) {
|
||||
neighbor.setNeighborUnloaded(-x, -z);
|
||||
chunk.setNeighborUnloaded(x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Moved from unloadChunks above
|
||||
chunk.removeEntities();
|
||||
if (save) {
|
||||
this.saveChunk(chunk, true); // Spigot
|
||||
this.saveChunkNOP(chunk);
|
||||
}
|
||||
this.chunks.remove(chunk.chunkKey);
|
||||
return true;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
public boolean e() {
|
||||
return !this.world.savingDisabled;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "ServerChunkCache: " + this.chunks.size() + " Drop: " + this.unloadQueue.size();
|
||||
}
|
||||
|
||||
public List<BiomeBase.BiomeMeta> a(EnumCreatureType enumcreaturetype, BlockPosition blockposition) {
|
||||
return this.chunkGenerator.getMobsFor(enumcreaturetype, blockposition);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPosition a(World world, String s, BlockPosition blockposition, boolean flag) {
|
||||
return this.chunkGenerator.findNearestMapFeature(world, s, blockposition, flag);
|
||||
}
|
||||
|
||||
public boolean a(World world, String s, BlockPosition blockposition) {
|
||||
return this.chunkGenerator.a(world, s, blockposition);
|
||||
}
|
||||
|
||||
public int g() {
|
||||
return this.chunks.size();
|
||||
}
|
||||
|
||||
public boolean isLoaded(int i, int j) {
|
||||
return this.chunks.containsKey(ChunkCoordIntPair.a(i, j));
|
||||
}
|
||||
|
||||
public boolean e(int i, int j) {
|
||||
return this.chunks.containsKey(ChunkCoordIntPair.a(i, j)) || this.chunkLoader.chunkExists(i, j);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue; // Paper
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
// Spigot start
|
||||
import java.util.function.Supplier;
|
||||
import org.spigotmc.SupplierUtils;
|
||||
// Spigot end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Removes unneed synchronization (performance)
|
||||
*/
|
||||
public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
|
||||
|
||||
private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>(); // Paper - Chunk queue improvements
|
||||
private final Object lock = new Object(); // Paper - Chunk queue improvements
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
private final Map<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = Maps.newConcurrentMap(); // Spigot
|
||||
// CraftBukkit
|
||||
// private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(Maps.newConcurrentMap());
|
||||
private final File d;
|
||||
private final DataConverterManager e;
|
||||
// private boolean f;
|
||||
// CraftBukkit
|
||||
private static final double SAVE_QUEUE_TARGET_SIZE = 625; // Spigot
|
||||
|
||||
public ChunkRegionLoader(File file, DataConverterManager dataconvertermanager) {
|
||||
this.d = file;
|
||||
this.e = dataconvertermanager;
|
||||
}
|
||||
|
||||
// Paper start
|
||||
private long queuedSaves = 0;
|
||||
private final java.util.concurrent.atomic.AtomicLong processedSaves = new java.util.concurrent.atomic.AtomicLong(0L);
|
||||
public int getQueueSize() { return queue.size(); }
|
||||
public long getQueuedSaves() { return queuedSaves; }
|
||||
public long getProcessedSaves() { return processedSaves.longValue(); }
|
||||
// Paper end
|
||||
|
||||
// CraftBukkit start - Add async variant, provide compatibility
|
||||
@Nullable
|
||||
public Chunk a(World world, int i, int j) throws IOException {
|
||||
world.timings.syncChunkLoadDataTimer.startTiming(); // Spigot
|
||||
Object[] data = loadChunk(world, i, j);
|
||||
world.timings.syncChunkLoadDataTimer.stopTiming(); // Spigot
|
||||
if (data != null) {
|
||||
Chunk chunk = (Chunk) data[0];
|
||||
NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
|
||||
loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object[] loadChunk(World world, int i, int j) throws IOException {
|
||||
// CraftBukkit end
|
||||
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
|
||||
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.b.get(chunkcoordintpair)); // Spigot
|
||||
|
||||
if (nbttagcompound == null) {
|
||||
// CraftBukkit start
|
||||
nbttagcompound = RegionFileCache.d(this.d, i, j);
|
||||
|
||||
if (nbttagcompound == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, nbttagcompound);
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
return this.a(world, i, j, nbttagcompound);
|
||||
}
|
||||
|
||||
public boolean chunkExists(int i, int j) {
|
||||
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
|
||||
Supplier<NBTTagCompound> nbttagcompound = this.b.get(chunkcoordintpair); // Spigot
|
||||
|
||||
return nbttagcompound != null ? true : RegionFileCache.chunkExists(this.d, i, j);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
|
||||
if (!nbttagcompound.hasKeyOfType("Level", 10)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
|
||||
return null;
|
||||
} else {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
|
||||
|
||||
if (!nbttagcompound1.hasKeyOfType("Sections", 9)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is missing block data, skipping", Integer.valueOf(i), Integer.valueOf(j));
|
||||
return null;
|
||||
} else {
|
||||
Chunk chunk = this.a(world, nbttagcompound1);
|
||||
|
||||
if (!chunk.a(i, j)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
|
||||
nbttagcompound1.setInt("xPos", i);
|
||||
nbttagcompound1.setInt("zPos", j);
|
||||
|
||||
// CraftBukkit start - Have to move tile entities since we don't load them at this stage
|
||||
NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
|
||||
if (tileEntities != null) {
|
||||
for (int te = 0; te < tileEntities.size(); te++) {
|
||||
NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
|
||||
int x = tileEntity.getInt("x") - chunk.locX * 16;
|
||||
int z = tileEntity.getInt("z") - chunk.locZ * 16;
|
||||
tileEntity.setInt("x", i * 16 + x);
|
||||
tileEntity.setInt("z", j * 16 + z);
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
chunk = this.a(world, nbttagcompound1);
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
Object[] data = new Object[2];
|
||||
data[0] = chunk;
|
||||
data[1] = nbttagcompound;
|
||||
return data;
|
||||
// CraftBukkit end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void saveChunk(World world, Chunk chunk, boolean unloaded) throws IOException, ExceptionWorldConflict { // Spigot
|
||||
world.checkSession();
|
||||
|
||||
try {
|
||||
NBTTagCompound nbttagcompound = new NBTTagCompound();
|
||||
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
|
||||
|
||||
nbttagcompound.set("Level", nbttagcompound1);
|
||||
nbttagcompound.setInt("DataVersion", 1343);
|
||||
|
||||
// Spigot start
|
||||
final long worldTime = world.getTime();
|
||||
final boolean worldHasSkyLight = world.worldProvider.m();
|
||||
saveEntities(nbttagcompound1, chunk, world);
|
||||
Supplier<NBTTagCompound> completion = new Supplier<NBTTagCompound>() {
|
||||
public NBTTagCompound get() {
|
||||
saveBody(nbttagcompound1, chunk, worldTime, worldHasSkyLight);
|
||||
return nbttagcompound;
|
||||
}
|
||||
};
|
||||
|
||||
this.a(chunk.k(), SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
|
||||
// Spigot end
|
||||
} catch (Exception exception) {
|
||||
ChunkRegionLoader.a.error("Failed to save chunk", exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
|
||||
// CraftBukkit
|
||||
// if (!this.c.contains(chunkcoordintpair))
|
||||
synchronized (lock) { // Paper - Chunk queue improvements
|
||||
this.b.put(chunkcoordintpair, nbttagcompound);
|
||||
}
|
||||
queuedSaves++; // Paper
|
||||
queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
|
||||
|
||||
FileIOThread.a().a(this);
|
||||
}
|
||||
|
||||
public boolean a() {
|
||||
// CraftBukkit start
|
||||
return this.processSaveQueueEntry(false);
|
||||
}
|
||||
|
||||
private /*synchronized*/ boolean processSaveQueueEntry(boolean logCompletion) { // Akarin - remove synchronization
|
||||
// CraftBukkit start
|
||||
// Paper start - Chunk queue improvements
|
||||
QueuedChunk chunk = queue.poll();
|
||||
if (chunk == null) {
|
||||
// Paper - end
|
||||
if (logCompletion) {
|
||||
// CraftBukkit end
|
||||
ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.d.getName());
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
|
||||
processedSaves.incrementAndGet(); // Paper
|
||||
|
||||
boolean flag;
|
||||
|
||||
try {
|
||||
// this.c.add(chunkcoordintpair);
|
||||
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(chunk.compoundSupplier); // Spigot // Paper
|
||||
// CraftBukkit
|
||||
|
||||
if (nbttagcompound != null) {
|
||||
int attempts = 0; Exception laste = null; while (attempts++ < 5) { // Paper
|
||||
try {
|
||||
this.b(chunkcoordintpair, nbttagcompound);
|
||||
laste = null; break; // Paper
|
||||
} catch (Exception exception) {
|
||||
//ChunkRegionLoader.a.error("Failed to save chunk", exception); // Paper
|
||||
laste = exception; // Paper
|
||||
}
|
||||
try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();} } // Paper
|
||||
if (laste != null) { com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste); MinecraftServer.LOGGER.error("Failed to save chunk", laste); } // Paper
|
||||
}
|
||||
synchronized (lock) { if (this.b.get(chunkcoordintpair) == chunk.compoundSupplier) { this.b.remove(chunkcoordintpair); } }// Paper - This will not equal if a newer version is still pending
|
||||
|
||||
flag = true;
|
||||
} finally {
|
||||
//this.b.remove(chunkcoordintpair, value); // CraftBukkit // Spigot // Paper
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
||||
private void b(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException {
|
||||
// CraftBukkit start
|
||||
RegionFileCache.e(this.d, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound);
|
||||
|
||||
/*
|
||||
NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
|
||||
dataoutputstream.close();
|
||||
*/
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
public void b(World world, Chunk chunk) throws IOException {}
|
||||
|
||||
public void b() {}
|
||||
|
||||
public void c() {
|
||||
try {
|
||||
// this.f = true; // CraftBukkit
|
||||
|
||||
while (true) {
|
||||
if (this.processSaveQueueEntry(true)) { // CraftBukkit
|
||||
continue;
|
||||
}
|
||||
break; // CraftBukkit - Fix infinite loop when saving chunks
|
||||
}
|
||||
} finally {
|
||||
// this.f = false; // CraftBukkit
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void a(DataConverterManager dataconvertermanager) {
|
||||
dataconvertermanager.a(DataConverterTypes.CHUNK, new DataInspector() {
|
||||
public NBTTagCompound a(DataConverter dataconverter, NBTTagCompound nbttagcompound, int i) {
|
||||
if (nbttagcompound.hasKeyOfType("Level", 10)) {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
|
||||
NBTTagList nbttaglist;
|
||||
int j;
|
||||
|
||||
if (nbttagcompound1.hasKeyOfType("Entities", 9)) {
|
||||
nbttaglist = nbttagcompound1.getList("Entities", 10);
|
||||
|
||||
for (j = 0; j < nbttaglist.size(); ++j) {
|
||||
nbttaglist.a(j, dataconverter.a(DataConverterTypes.ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
|
||||
}
|
||||
}
|
||||
|
||||
if (nbttagcompound1.hasKeyOfType("TileEntities", 9)) {
|
||||
nbttaglist = nbttagcompound1.getList("TileEntities", 10);
|
||||
|
||||
for (j = 0; j < nbttaglist.size(); ++j) {
|
||||
nbttaglist.a(j, dataconverter.a(DataConverterTypes.BLOCK_ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nbttagcompound;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void saveBody(NBTTagCompound nbttagcompound, Chunk chunk, long worldTime, boolean worldHasSkyLight) { // Spigot
|
||||
nbttagcompound.setInt("xPos", chunk.locX);
|
||||
nbttagcompound.setInt("zPos", chunk.locZ);
|
||||
nbttagcompound.setLong("LastUpdate", worldTime); // Spigot
|
||||
nbttagcompound.setIntArray("HeightMap", chunk.r());
|
||||
nbttagcompound.setBoolean("TerrainPopulated", chunk.isDone());
|
||||
nbttagcompound.setBoolean("LightPopulated", chunk.v());
|
||||
nbttagcompound.setLong("InhabitedTime", chunk.x());
|
||||
ChunkSection[] achunksection = chunk.getSections();
|
||||
NBTTagList nbttaglist = new NBTTagList();
|
||||
boolean flag = worldHasSkyLight; // Spigot
|
||||
ChunkSection[] achunksection1 = achunksection;
|
||||
int i = achunksection.length;
|
||||
|
||||
NBTTagCompound nbttagcompound1;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
ChunkSection chunksection = achunksection1[j];
|
||||
|
||||
if (chunksection != Chunk.a) {
|
||||
nbttagcompound1 = new NBTTagCompound();
|
||||
nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255));
|
||||
byte[] abyte = new byte[4096];
|
||||
NibbleArray nibblearray = new NibbleArray();
|
||||
NibbleArray nibblearray1 = chunksection.getBlocks().exportData(abyte, nibblearray);
|
||||
|
||||
nbttagcompound1.setByteArray("Blocks", abyte);
|
||||
nbttagcompound1.setByteArray("Data", nibblearray.asBytes());
|
||||
if (nibblearray1 != null) {
|
||||
nbttagcompound1.setByteArray("Add", nibblearray1.asBytes());
|
||||
}
|
||||
|
||||
nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().asBytes());
|
||||
if (flag) {
|
||||
nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().asBytes());
|
||||
} else {
|
||||
nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().asBytes().length]);
|
||||
}
|
||||
|
||||
nbttaglist.add(nbttagcompound1);
|
||||
}
|
||||
}
|
||||
|
||||
nbttagcompound.set("Sections", nbttaglist);
|
||||
nbttagcompound.setByteArray("Biomes", chunk.getBiomeIndex());
|
||||
|
||||
// Spigot start - End this method here and split off entity saving to another method
|
||||
}
|
||||
|
||||
private static void saveEntities(NBTTagCompound nbttagcompound, Chunk chunk, World world) {
|
||||
int i;
|
||||
NBTTagCompound nbttagcompound1;
|
||||
// Spigot end
|
||||
|
||||
chunk.g(false);
|
||||
NBTTagList nbttaglist1 = new NBTTagList();
|
||||
|
||||
Iterator iterator;
|
||||
|
||||
List<Entity> toUpdate = new java.util.ArrayList<>(); // Paper
|
||||
for (i = 0; i < chunk.getEntitySlices().length; ++i) {
|
||||
iterator = chunk.getEntitySlices()[i].iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity = (Entity) iterator.next();
|
||||
// Paper start
|
||||
if ((int)Math.floor(entity.locX) >> 4 != chunk.locX || (int)Math.floor(entity.locZ) >> 4 != chunk.locZ) {
|
||||
LogManager.getLogger().warn(entity + " is not in this chunk, skipping save. This a bug fix to a vanilla bug. Do not report this to PaperMC please.");
|
||||
toUpdate.add(entity);
|
||||
continue;
|
||||
}
|
||||
if (entity.dead) {
|
||||
continue;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
nbttagcompound1 = new NBTTagCompound();
|
||||
if (entity.d(nbttagcompound1)) {
|
||||
chunk.g(true);
|
||||
nbttaglist1.add(nbttagcompound1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Paper start - move entities to the correct chunk
|
||||
for (Entity entity : toUpdate) {
|
||||
world.entityJoinedWorld(entity, false);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
nbttagcompound.set("Entities", nbttaglist1);
|
||||
NBTTagList nbttaglist2 = new NBTTagList();
|
||||
|
||||
iterator = chunk.getTileEntities().values().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
TileEntity tileentity = (TileEntity) iterator.next();
|
||||
|
||||
nbttagcompound1 = tileentity.save(new NBTTagCompound());
|
||||
nbttaglist2.add(nbttagcompound1);
|
||||
}
|
||||
|
||||
nbttagcompound.set("TileEntities", nbttaglist2);
|
||||
List list = world.a(chunk, false);
|
||||
|
||||
if (list != null) {
|
||||
long k = world.getTime();
|
||||
NBTTagList nbttaglist3 = new NBTTagList();
|
||||
Iterator iterator1 = list.iterator();
|
||||
|
||||
while (iterator1.hasNext()) {
|
||||
NextTickListEntry nextticklistentry = (NextTickListEntry) iterator1.next();
|
||||
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
|
||||
MinecraftKey minecraftkey = (MinecraftKey) Block.REGISTRY.b(nextticklistentry.a());
|
||||
|
||||
nbttagcompound2.setString("i", minecraftkey == null ? "" : minecraftkey.toString());
|
||||
nbttagcompound2.setInt("x", nextticklistentry.a.getX());
|
||||
nbttagcompound2.setInt("y", nextticklistentry.a.getY());
|
||||
nbttagcompound2.setInt("z", nextticklistentry.a.getZ());
|
||||
nbttagcompound2.setInt("t", (int) (nextticklistentry.b - k));
|
||||
nbttagcompound2.setInt("p", nextticklistentry.c);
|
||||
nbttaglist3.add(nbttagcompound2);
|
||||
}
|
||||
|
||||
nbttagcompound.set("TileTicks", nbttaglist3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Chunk a(World world, NBTTagCompound nbttagcompound) {
|
||||
int i = nbttagcompound.getInt("xPos");
|
||||
int j = nbttagcompound.getInt("zPos");
|
||||
Chunk chunk = new Chunk(world, i, j);
|
||||
|
||||
chunk.a(nbttagcompound.getIntArray("HeightMap"));
|
||||
chunk.d(nbttagcompound.getBoolean("TerrainPopulated"));
|
||||
chunk.e(nbttagcompound.getBoolean("LightPopulated"));
|
||||
chunk.c(nbttagcompound.getLong("InhabitedTime"));
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Sections", 10);
|
||||
boolean flag = true;
|
||||
ChunkSection[] achunksection = new ChunkSection[16];
|
||||
boolean flag1 = world.worldProvider.m();
|
||||
|
||||
for (int k = 0; k < nbttaglist.size(); ++k) {
|
||||
NBTTagCompound nbttagcompound1 = nbttaglist.get(k);
|
||||
byte b0 = nbttagcompound1.getByte("Y");
|
||||
ChunkSection chunksection = new ChunkSection(b0 << 4, flag1, world.chunkPacketBlockController.getPredefinedBlockData(chunk, b0)); // Paper - Anti-Xray - Add predefined block data
|
||||
byte[] abyte = nbttagcompound1.getByteArray("Blocks");
|
||||
NibbleArray nibblearray = new NibbleArray(nbttagcompound1.getByteArray("Data"));
|
||||
NibbleArray nibblearray1 = nbttagcompound1.hasKeyOfType("Add", 7) ? new NibbleArray(nbttagcompound1.getByteArray("Add")) : null;
|
||||
|
||||
chunksection.getBlocks().a(abyte, nibblearray, nibblearray1);
|
||||
chunksection.a(new NibbleArray(nbttagcompound1.getByteArray("BlockLight")));
|
||||
if (flag1) {
|
||||
chunksection.b(new NibbleArray(nbttagcompound1.getByteArray("SkyLight")));
|
||||
}
|
||||
|
||||
chunksection.recalcBlockCounts();
|
||||
achunksection[b0] = chunksection;
|
||||
}
|
||||
|
||||
chunk.a(achunksection);
|
||||
if (nbttagcompound.hasKeyOfType("Biomes", 7)) {
|
||||
chunk.a(nbttagcompound.getByteArray("Biomes"));
|
||||
}
|
||||
|
||||
// CraftBukkit start - End this method here and split off entity loading to another method
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
|
||||
// CraftBukkit end
|
||||
world.timings.syncChunkLoadNBTTimer.startTiming(); // Spigot
|
||||
NBTTagList nbttaglist1 = nbttagcompound.getList("Entities", 10);
|
||||
|
||||
for (int l = 0; l < nbttaglist1.size(); ++l) {
|
||||
NBTTagCompound nbttagcompound2 = nbttaglist1.get(l);
|
||||
|
||||
a(nbttagcompound2, world, chunk);
|
||||
chunk.g(true);
|
||||
}
|
||||
NBTTagList nbttaglist2 = nbttagcompound.getList("TileEntities", 10);
|
||||
|
||||
for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
|
||||
NBTTagCompound nbttagcompound3 = nbttaglist2.get(i1);
|
||||
TileEntity tileentity = TileEntity.create(world, nbttagcompound3);
|
||||
|
||||
if (tileentity != null) {
|
||||
chunk.a(tileentity);
|
||||
}
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("TileTicks", 9)) {
|
||||
NBTTagList nbttaglist3 = nbttagcompound.getList("TileTicks", 10);
|
||||
|
||||
for (int j1 = 0; j1 < nbttaglist3.size(); ++j1) {
|
||||
NBTTagCompound nbttagcompound4 = nbttaglist3.get(j1);
|
||||
Block block;
|
||||
|
||||
if (nbttagcompound4.hasKeyOfType("i", 8)) {
|
||||
block = Block.getByName(nbttagcompound4.getString("i"));
|
||||
} else {
|
||||
block = Block.getById(nbttagcompound4.getInt("i"));
|
||||
}
|
||||
|
||||
world.b(new BlockPosition(nbttagcompound4.getInt("x"), nbttagcompound4.getInt("y"), nbttagcompound4.getInt("z")), block, nbttagcompound4.getInt("t"), nbttagcompound4.getInt("p"));
|
||||
}
|
||||
}
|
||||
world.timings.syncChunkLoadNBTTimer.stopTiming(); // Spigot
|
||||
|
||||
// return chunk; // CraftBukkit
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, Chunk chunk) {
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
chunk.a(entity);
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, chunk);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
// CraftBukkit start
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag) {
|
||||
return spawnEntity(nbttagcompound, world, d0, d1, d2, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
|
||||
}
|
||||
|
||||
public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
|
||||
// CraftBukkit end
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
entity.setPositionRotation(d0, d1, d2, entity.yaw, entity.pitch);
|
||||
if (flag && !world.addEntity(entity, spawnReason)) { // CraftBukkit
|
||||
return null;
|
||||
} else {
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, d0, d1, d2, flag);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static Entity a(NBTTagCompound nbttagcompound, World world) {
|
||||
try {
|
||||
return EntityTypes.a(nbttagcompound, world);
|
||||
} catch (RuntimeException runtimeexception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
public static void a(Entity entity, World world) {
|
||||
a(entity, world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
|
||||
}
|
||||
|
||||
public static void a(Entity entity, World world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
|
||||
if (!entity.valid && world.addEntity(entity, reason) && entity.isVehicle()) { // Paper
|
||||
// CraftBukkit end
|
||||
Iterator iterator = entity.bF().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity1 = (Entity) iterator.next();
|
||||
|
||||
a(entity1, world);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, boolean flag) {
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else if (flag && !world.addEntity(entity)) {
|
||||
return null;
|
||||
} else {
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, flag);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
// Paper start - Chunk queue improvements
|
||||
private static class QueuedChunk {
|
||||
public ChunkCoordIntPair coords;
|
||||
public Supplier<NBTTagCompound> compoundSupplier;
|
||||
|
||||
public QueuedChunk(ChunkCoordIntPair coords, Supplier<NBTTagCompound> compoundSupplier) {
|
||||
this.coords = coords;
|
||||
this.compoundSupplier = compoundSupplier;
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
115
sources/src/main/java/net/minecraft/server/ContainerHorse.java
Normal file
115
sources/src/main/java/net/minecraft/server/ContainerHorse.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
// CraftBukkit start
|
||||
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
// CraftBukkit end
|
||||
|
||||
public class ContainerHorse extends Container {
|
||||
|
||||
private final IInventory a;
|
||||
private final EntityHorseAbstract f;
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
|
||||
PlayerInventory player;
|
||||
|
||||
@Override
|
||||
public InventoryView getBukkitView() {
|
||||
if (bukkitEntity != null) {
|
||||
return bukkitEntity;
|
||||
}
|
||||
|
||||
return bukkitEntity = new CraftInventoryView(player.player.getBukkitEntity(), a.getOwner().getInventory(), this);
|
||||
}
|
||||
|
||||
public ContainerHorse(IInventory iinventory, final IInventory iinventory1, final EntityHorseAbstract entityhorseabstract, EntityHuman entityhuman) {
|
||||
player = (PlayerInventory) iinventory;
|
||||
// CraftBukkit end
|
||||
this.a = iinventory1;
|
||||
this.f = entityhorseabstract;
|
||||
boolean flag = true;
|
||||
|
||||
iinventory1.startOpen(entityhuman);
|
||||
boolean flag1 = true;
|
||||
|
||||
this.a(new Slot(iinventory1, 0, 8, 18) {
|
||||
public boolean isAllowed(ItemStack itemstack) {
|
||||
return itemstack.getItem() == Items.SADDLE && !this.hasItem() && entityhorseabstract.dF();
|
||||
}
|
||||
});
|
||||
this.a(new Slot(iinventory1, 1, 8, 36) {
|
||||
public boolean isAllowed(ItemStack itemstack) {
|
||||
return entityhorseabstract.f(itemstack);
|
||||
}
|
||||
|
||||
public int getMaxStackSize() {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
int i;
|
||||
int j;
|
||||
|
||||
if (entityhorseabstract instanceof EntityHorseChestedAbstract && ((EntityHorseChestedAbstract) entityhorseabstract).isCarryingChest()) {
|
||||
for (i = 0; i < 3; ++i) {
|
||||
for (j = 0; j < ((EntityHorseChestedAbstract) entityhorseabstract).dt(); ++j) {
|
||||
this.a(new Slot(iinventory1, 2 + j + i * ((EntityHorseChestedAbstract) entityhorseabstract).dt(), 80 + j * 18, 18 + i * 18));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; ++i) {
|
||||
for (j = 0; j < 9; ++j) {
|
||||
this.a(new Slot(iinventory, j + i * 9 + 9, 8 + j * 18, 102 + i * 18 + -18));
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 9; ++i) {
|
||||
this.a(new Slot(iinventory, i, 8 + i * 18, 142));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean canUse(EntityHuman entityhuman) {
|
||||
return this.a.a(entityhuman) && this.f.isAlive() && this.f.valid && this.f.g((Entity) entityhuman) < 8.0F; // NeonPaper! - Fix MC-161754
|
||||
}
|
||||
|
||||
public ItemStack shiftClick(EntityHuman entityhuman, int i) {
|
||||
ItemStack itemstack = ItemStack.a;
|
||||
Slot slot = (Slot) this.slots.get(i);
|
||||
|
||||
if (slot != null && slot.hasItem()) {
|
||||
ItemStack itemstack1 = slot.getItem();
|
||||
|
||||
itemstack = itemstack1.cloneItemStack();
|
||||
if (i < this.a.getSize()) {
|
||||
if (!this.a(itemstack1, this.a.getSize(), this.slots.size(), true)) {
|
||||
return ItemStack.a;
|
||||
}
|
||||
} else if (this.getSlot(1).isAllowed(itemstack1) && !this.getSlot(1).hasItem()) {
|
||||
if (!this.a(itemstack1, 1, 2, false)) {
|
||||
return ItemStack.a;
|
||||
}
|
||||
} else if (this.getSlot(0).isAllowed(itemstack1)) {
|
||||
if (!this.a(itemstack1, 0, 1, false)) {
|
||||
return ItemStack.a;
|
||||
}
|
||||
} else if (this.a.getSize() <= 2 || !this.a(itemstack1, 2, this.a.getSize(), false)) {
|
||||
return ItemStack.a;
|
||||
}
|
||||
|
||||
if (itemstack1.isEmpty()) {
|
||||
slot.set(ItemStack.a);
|
||||
} else {
|
||||
slot.f();
|
||||
}
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
public void b(EntityHuman entityhuman) {
|
||||
super.b(entityhuman);
|
||||
this.a.closeContainer(entityhuman);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,868 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
// CraftBukkit start
|
||||
import java.util.List;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.TreeType;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.event.block.BlockDispenseEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
// CraftBukkit end
|
||||
|
||||
public class DispenserRegistry {
|
||||
|
||||
public static final PrintStream a = System.out;
|
||||
private static boolean c;
|
||||
public static boolean b;
|
||||
private static final Logger d = LogManager.getLogger();
|
||||
|
||||
public static boolean a() {
|
||||
return DispenserRegistry.c;
|
||||
}
|
||||
|
||||
static void b() {
|
||||
BlockDispenser.REGISTRY.a(Items.ARROW, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
EntityTippedArrow entitytippedarrow = new EntityTippedArrow(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
|
||||
entitytippedarrow.fromPlayer = EntityArrow.PickupStatus.ALLOWED;
|
||||
return entitytippedarrow;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.TIPPED_ARROW, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
EntityTippedArrow entitytippedarrow = new EntityTippedArrow(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
|
||||
entitytippedarrow.a(itemstack);
|
||||
entitytippedarrow.fromPlayer = EntityArrow.PickupStatus.ALLOWED;
|
||||
return entitytippedarrow;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.SPECTRAL_ARROW, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
EntitySpectralArrow entityspectralarrow = new EntitySpectralArrow(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
|
||||
entityspectralarrow.fromPlayer = EntityArrow.PickupStatus.ALLOWED;
|
||||
return entityspectralarrow;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.EGG, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
return new EntityEgg(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.SNOWBALL, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
return new EntitySnowball(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.EXPERIENCE_BOTTLE, new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack) {
|
||||
return new EntityThrownExpBottle(world, iposition.getX(), iposition.getY(), iposition.getZ());
|
||||
}
|
||||
|
||||
protected float a() {
|
||||
return super.a() * 0.5F;
|
||||
}
|
||||
|
||||
protected float getPower() {
|
||||
return super.getPower() * 1.25F;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.SPLASH_POTION, new IDispenseBehavior() {
|
||||
public ItemStack a(ISourceBlock isourceblock, final ItemStack itemstack) {
|
||||
return (new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack1) { // CraftBukkit - decompile issue
|
||||
return new EntityPotion(world, iposition.getX(), iposition.getY(), iposition.getZ(), itemstack1.cloneItemStack());
|
||||
}
|
||||
|
||||
protected float a() {
|
||||
return super.a() * 0.5F;
|
||||
}
|
||||
|
||||
protected float getPower() {
|
||||
return super.getPower() * 1.25F;
|
||||
}
|
||||
}).a(isourceblock, itemstack);
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.LINGERING_POTION, new IDispenseBehavior() {
|
||||
public ItemStack a(ISourceBlock isourceblock, final ItemStack itemstack) {
|
||||
return (new DispenseBehaviorProjectile() {
|
||||
protected IProjectile a(World world, IPosition iposition, ItemStack itemstack1) { // CraftBukkit - decompile issue
|
||||
return new EntityPotion(world, iposition.getX(), iposition.getY(), iposition.getZ(), itemstack1.cloneItemStack());
|
||||
}
|
||||
|
||||
protected float a() {
|
||||
return super.a() * 0.5F;
|
||||
}
|
||||
|
||||
protected float getPower() {
|
||||
return super.getPower() * 1.25F;
|
||||
}
|
||||
}).a(isourceblock, itemstack);
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.SPAWN_EGG, new DispenseBehaviorItem() {
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
double d0 = isourceblock.getX() + (double) enumdirection.getAdjacentX();
|
||||
double d1 = (double) ((float) (isourceblock.getBlockPosition().getY() + enumdirection.getAdjacentY()) + 0.2F);
|
||||
double d2 = isourceblock.getZ() + (double) enumdirection.getAdjacentZ();
|
||||
// Entity entity = ItemMonsterEgg.a(isourceblock.getWorld(), ItemMonsterEgg.h(itemstack), d0, d1, d2);
|
||||
|
||||
// CraftBukkit start
|
||||
World world = isourceblock.getWorld();
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1, d2));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
itemstack.add(1);
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
itemstack.add(1);
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
|
||||
|
||||
Entity entity = ItemMonsterEgg.spawnCreature(isourceblock.getWorld(), ItemMonsterEgg.h(itemstack), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG);
|
||||
|
||||
if (entity instanceof EntityLiving && itemstack.hasName()) {
|
||||
entity.setCustomName(itemstack.getName());
|
||||
}
|
||||
|
||||
ItemMonsterEgg.a(isourceblock.getWorld(), (EntityHuman) null, itemstack, entity);
|
||||
// itemstack.subtract(1);// Handled during event processing
|
||||
// CraftBukkit end
|
||||
return itemstack;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.FIREWORKS, new DispenseBehaviorItem() {
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
double d0 = isourceblock.getX() + (double) enumdirection.getAdjacentX();
|
||||
double d1 = (double) ((float) isourceblock.getBlockPosition().getY() + 0.2F);
|
||||
double d2 = isourceblock.getZ() + (double) enumdirection.getAdjacentZ();
|
||||
// CraftBukkit start
|
||||
World world = isourceblock.getWorld();
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1, d2));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
itemstack.add(1);
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
itemstack.add(1);
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
|
||||
EntityFireworks entityfireworks = new EntityFireworks(isourceblock.getWorld(), event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), itemstack1);
|
||||
|
||||
isourceblock.getWorld().addEntity(entityfireworks);
|
||||
// itemstack.subtract(1); // Handled during event processing
|
||||
// CraftBukkit end
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
protected void a(ISourceBlock isourceblock) {
|
||||
isourceblock.getWorld().triggerEffect(1004, isourceblock.getBlockPosition(), 0);
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.FIRE_CHARGE, new DispenseBehaviorItem() {
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
IPosition iposition = BlockDispenser.a(isourceblock);
|
||||
double d0 = iposition.getX() + (double) ((float) enumdirection.getAdjacentX() * 0.3F);
|
||||
double d1 = iposition.getY() + (double) ((float) enumdirection.getAdjacentY() * 0.3F);
|
||||
double d2 = iposition.getZ() + (double) ((float) enumdirection.getAdjacentZ() * 0.3F);
|
||||
World world = isourceblock.getWorld();
|
||||
Random random = world.random;
|
||||
double d3 = random.nextGaussian() * 0.05D + (double) enumdirection.getAdjacentX();
|
||||
double d4 = random.nextGaussian() * 0.05D + (double) enumdirection.getAdjacentY();
|
||||
double d5 = random.nextGaussian() * 0.05D + (double) enumdirection.getAdjacentZ();
|
||||
|
||||
// CraftBukkit start
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
itemstack.add(1);
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
itemstack.add(1);
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
EntitySmallFireball fireball = new EntitySmallFireball(world, d0, d1, d2, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
|
||||
fireball.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource((TileEntityDispenser) isourceblock.getTileEntity());
|
||||
|
||||
world.addEntity(fireball);
|
||||
// itemstack.subtract(1); // Handled during event processing
|
||||
// CraftBukkit end
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
protected void a(ISourceBlock isourceblock) {
|
||||
isourceblock.getWorld().triggerEffect(1018, isourceblock.getBlockPosition(), 0);
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.aH, new DispenserRegistry.a(EntityBoat.EnumBoatType.OAK));
|
||||
BlockDispenser.REGISTRY.a(Items.aI, new DispenserRegistry.a(EntityBoat.EnumBoatType.SPRUCE));
|
||||
BlockDispenser.REGISTRY.a(Items.aJ, new DispenserRegistry.a(EntityBoat.EnumBoatType.BIRCH));
|
||||
BlockDispenser.REGISTRY.a(Items.aK, new DispenserRegistry.a(EntityBoat.EnumBoatType.JUNGLE));
|
||||
BlockDispenser.REGISTRY.a(Items.aM, new DispenserRegistry.a(EntityBoat.EnumBoatType.DARK_OAK));
|
||||
BlockDispenser.REGISTRY.a(Items.aL, new DispenserRegistry.a(EntityBoat.EnumBoatType.ACACIA));
|
||||
DispenseBehaviorItem dispensebehavioritem = new DispenseBehaviorItem() {
|
||||
private final DispenseBehaviorItem b = new DispenseBehaviorItem();
|
||||
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
ItemBucket itembucket = (ItemBucket) itemstack.getItem();
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
|
||||
// CraftBukkit start
|
||||
World world = isourceblock.getWorld();
|
||||
int x = blockposition.getX();
|
||||
int y = blockposition.getY();
|
||||
int z = blockposition.getZ();
|
||||
if (world.isEmpty(blockposition) || !world.getType(blockposition).getMaterial().isBuildable()) {
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
itembucket = (ItemBucket) CraftItemStack.asNMSCopy(event.getItem()).getItem();
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
if (itembucket.a((EntityHuman) null, isourceblock.getWorld(), blockposition)) {
|
||||
// CraftBukkit start - Handle stacked buckets
|
||||
Item item = Items.BUCKET;
|
||||
itemstack.subtract(1);
|
||||
if (itemstack.isEmpty()) {
|
||||
itemstack.setItem(Items.BUCKET);
|
||||
itemstack.setCount(1);
|
||||
} else if (((TileEntityDispenser) isourceblock.getTileEntity()).addItem(new ItemStack(item)) < 0) {
|
||||
this.b.a(isourceblock, new ItemStack(item));
|
||||
}
|
||||
// CraftBukkit end
|
||||
return itemstack;
|
||||
} else {
|
||||
return this.b.a(isourceblock, itemstack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BlockDispenser.REGISTRY.a(Items.LAVA_BUCKET, dispensebehavioritem);
|
||||
BlockDispenser.REGISTRY.a(Items.WATER_BUCKET, dispensebehavioritem);
|
||||
BlockDispenser.REGISTRY.a(Items.BUCKET, new DispenseBehaviorItem() {
|
||||
private final DispenseBehaviorItem b = new DispenseBehaviorItem();
|
||||
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
World world = isourceblock.getWorld();
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
IBlockData iblockdata = world.getType(blockposition);
|
||||
Block block = iblockdata.getBlock();
|
||||
Material material = iblockdata.getMaterial();
|
||||
Item item;
|
||||
|
||||
if (Material.WATER.equals(material) && block instanceof BlockFluids && ((Integer) iblockdata.get(BlockFluids.LEVEL)).intValue() == 0) {
|
||||
item = Items.WATER_BUCKET;
|
||||
} else {
|
||||
if (!Material.LAVA.equals(material) || !(block instanceof BlockFluids) || ((Integer) iblockdata.get(BlockFluids.LEVEL)).intValue() != 0) {
|
||||
return super.b(isourceblock, itemstack);
|
||||
}
|
||||
|
||||
item = Items.LAVA_BUCKET;
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
world.setAir(blockposition);
|
||||
itemstack.subtract(1);
|
||||
if (itemstack.isEmpty()) {
|
||||
return new ItemStack(item);
|
||||
} else {
|
||||
if (((TileEntityDispenser) isourceblock.getTileEntity()).addItem(new ItemStack(item)) < 0) {
|
||||
this.b.a(isourceblock, new ItemStack(item));
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.FLINT_AND_STEEL, new DispenserRegistry.b() {
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
World world = isourceblock.getWorld();
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
this.b = true;
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
|
||||
if (world.isEmpty(blockposition)) {
|
||||
// CraftBukkit start - Ignition by dispensing flint and steel
|
||||
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ()).isCancelled()) {
|
||||
world.setTypeUpdate(blockposition, Blocks.FIRE.getBlockData());
|
||||
if (itemstack.isDamaged(1, world.random, (EntityPlayer) null)) {
|
||||
itemstack.setCount(0);
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
} else if (world.getType(blockposition).getBlock() == Blocks.TNT) {
|
||||
Blocks.TNT.postBreak(world, blockposition, Blocks.TNT.getBlockData().set(BlockTNT.EXPLODE, Boolean.valueOf(true)));
|
||||
world.setAir(blockposition);
|
||||
} else {
|
||||
this.b = false;
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.DYE, new DispenserRegistry.b() {
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
this.b = true;
|
||||
if (EnumColor.WHITE == EnumColor.fromInvColorIndex(itemstack.getData())) {
|
||||
World world = isourceblock.getWorld();
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
world.captureTreeGeneration = true;
|
||||
// CraftBukkit end
|
||||
|
||||
if (ItemDye.a(itemstack, world, blockposition)) {
|
||||
if (!world.isClientSide) {
|
||||
world.triggerEffect(2005, blockposition, 0);
|
||||
}
|
||||
} else {
|
||||
this.b = false;
|
||||
}
|
||||
// CraftBukkit start
|
||||
world.captureTreeGeneration = false;
|
||||
if (world.capturedBlockStates.size() > 0) {
|
||||
TreeType treeType = BlockSapling.treeType;
|
||||
BlockSapling.treeType = null;
|
||||
Location location = new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ());
|
||||
List<org.bukkit.block.BlockState> blocks = (List<org.bukkit.block.BlockState>) world.capturedBlockStates.clone();
|
||||
world.capturedBlockStates.clear();
|
||||
StructureGrowEvent structureEvent = null;
|
||||
if (treeType != null) {
|
||||
structureEvent = new StructureGrowEvent(location, treeType, false, null, blocks);
|
||||
org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
|
||||
}
|
||||
if (structureEvent == null || !structureEvent.isCancelled()) {
|
||||
for (org.bukkit.block.BlockState blockstate : blocks) {
|
||||
blockstate.update(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
return itemstack;
|
||||
} else {
|
||||
return super.b(isourceblock, itemstack);
|
||||
}
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Item.getItemOf(Blocks.TNT), new DispenseBehaviorItem() {
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
World world = isourceblock.getWorld();
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
// EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null);
|
||||
|
||||
// CraftBukkit start
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
itemstack.add(1);
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
itemstack.add(1);
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (EntityLiving) null);
|
||||
// CraftBukkit end
|
||||
|
||||
world.addEntity(entitytntprimed);
|
||||
world.a((EntityHuman) null, entitytntprimed.locX, entitytntprimed.locY, entitytntprimed.locZ, SoundEffects.hW, SoundCategory.BLOCKS, 1.0F, 1.0F);
|
||||
// itemstack.subtract(1); // CraftBukkit - handled above
|
||||
return itemstack;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Items.SKULL, new DispenserRegistry.b() {
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
World world = isourceblock.getWorld();
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift(enumdirection);
|
||||
BlockSkull blockskull = Blocks.SKULL;
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
this.b = true;
|
||||
if (world.isEmpty(blockposition) && blockskull.b(world, blockposition, itemstack)) {
|
||||
if (!world.isClientSide) {
|
||||
world.setTypeAndData(blockposition, blockskull.getBlockData().set(BlockSkull.FACING, EnumDirection.UP), 3);
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntitySkull) {
|
||||
if (itemstack.getData() == 3) {
|
||||
GameProfile gameprofile = null;
|
||||
|
||||
if (itemstack.hasTag()) {
|
||||
NBTTagCompound nbttagcompound = itemstack.getTag();
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("SkullOwner", 10)) {
|
||||
gameprofile = GameProfileSerializer.deserialize(nbttagcompound.getCompound("SkullOwner"));
|
||||
} else if (nbttagcompound.hasKeyOfType("SkullOwner", 8)) {
|
||||
String s = nbttagcompound.getString("SkullOwner");
|
||||
|
||||
if (!UtilColor.b(s)) {
|
||||
gameprofile = new GameProfile((UUID) null, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((TileEntitySkull) tileentity).setGameProfile(gameprofile);
|
||||
} else {
|
||||
((TileEntitySkull) tileentity).setSkullType(itemstack.getData());
|
||||
}
|
||||
|
||||
((TileEntitySkull) tileentity).setRotation(enumdirection.opposite().get2DRotationValue() * 4);
|
||||
Blocks.SKULL.a(world, blockposition, (TileEntitySkull) tileentity);
|
||||
}
|
||||
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
} else if (ItemArmor.a(isourceblock, itemstack).isEmpty()) {
|
||||
this.b = false;
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
});
|
||||
BlockDispenser.REGISTRY.a(Item.getItemOf(Blocks.PUMPKIN), new DispenserRegistry.b() {
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
World world = isourceblock.getWorld();
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift((EnumDirection) isourceblock.e().get(BlockDispenser.FACING));
|
||||
BlockPumpkin blockpumpkin = (BlockPumpkin) Blocks.PUMPKIN;
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
this.b = true;
|
||||
if (world.isEmpty(blockposition) && blockpumpkin.b(world, blockposition)) {
|
||||
if (!world.isClientSide) {
|
||||
world.setTypeAndData(blockposition, blockpumpkin.getBlockData(), 3);
|
||||
}
|
||||
|
||||
itemstack.subtract(1);
|
||||
} else {
|
||||
ItemStack itemstack1 = ItemArmor.a(isourceblock, itemstack);
|
||||
|
||||
if (itemstack1.isEmpty()) {
|
||||
this.b = false;
|
||||
}
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
});
|
||||
EnumColor[] aenumcolor = EnumColor.values();
|
||||
int i = aenumcolor.length;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
EnumColor enumcolor = aenumcolor[j];
|
||||
|
||||
BlockDispenser.REGISTRY.a(Item.getItemOf(BlockShulkerBox.a(enumcolor)), new DispenserRegistry.c(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void c() {
|
||||
if (!DispenserRegistry.c) {
|
||||
DispenserRegistry.c = true;
|
||||
d();
|
||||
SoundEffect.b();
|
||||
Block.w();
|
||||
BlockFire.e();
|
||||
MobEffectList.k();
|
||||
Enchantment.g();
|
||||
Item.t();
|
||||
PotionRegistry.b();
|
||||
PotionBrewer.a();
|
||||
EntityTypes.c();
|
||||
BiomeBase.q();
|
||||
b();
|
||||
if (!CraftingManager.init()) {
|
||||
DispenserRegistry.b = true;
|
||||
DispenserRegistry.d.error("Errors with built-in recipes!");
|
||||
}
|
||||
|
||||
StatisticList.a();
|
||||
if (DispenserRegistry.d.isDebugEnabled()) {
|
||||
if ((new AdvancementDataWorld((File) null)).b()) {
|
||||
DispenserRegistry.b = true;
|
||||
DispenserRegistry.d.error("Errors with built-in advancements!");
|
||||
}
|
||||
|
||||
if (!LootTables.b()) {
|
||||
DispenserRegistry.b = true;
|
||||
DispenserRegistry.d.error("Errors with built-in loot tables");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void d() {
|
||||
if (DispenserRegistry.d.isDebugEnabled()) {
|
||||
System.setErr(new DebugOutputStream("STDERR", System.err));
|
||||
System.setOut(new DebugOutputStream("STDOUT", DispenserRegistry.a));
|
||||
} else {
|
||||
System.setErr(new RedirectStream("STDERR", System.err));
|
||||
System.setOut(new RedirectStream("STDOUT", DispenserRegistry.a));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class c extends DispenserRegistry.b {
|
||||
|
||||
private c() {}
|
||||
|
||||
protected ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
Block block = Block.asBlock(itemstack.getItem());
|
||||
World world = isourceblock.getWorld();
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift(enumdirection);
|
||||
|
||||
// CraftBukkit start
|
||||
org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
this.b = world.a(block, blockposition, false, EnumDirection.DOWN, (Entity) null);
|
||||
if (this.b) {
|
||||
EnumDirection enumdirection1 = world.isEmpty(blockposition.down()) ? enumdirection : EnumDirection.UP;
|
||||
IBlockData iblockdata = block.getBlockData().set(BlockShulkerBox.a, enumdirection1);
|
||||
// Dionysus start - fix Dispenser crashes
|
||||
boolean wasPlaced = world.setTypeUpdate(blockposition, iblockdata);
|
||||
if (!wasPlaced) {
|
||||
return itemstack;
|
||||
}
|
||||
// Dionysus end
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
|
||||
if (itemstack1.hasTag()) {
|
||||
((TileEntityShulkerBox) tileentity).e(itemstack1.getTag().getCompound("BlockEntityTag"));
|
||||
}
|
||||
|
||||
if (itemstack1.hasName()) {
|
||||
((TileEntityShulkerBox) tileentity).setCustomName(itemstack1.getName());
|
||||
}
|
||||
|
||||
world.updateAdjacentComparators(blockposition, iblockdata.getBlock());
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
c(Object object) {
|
||||
this();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class b extends DispenseBehaviorItem {
|
||||
|
||||
protected boolean b = true;
|
||||
|
||||
public b() {}
|
||||
|
||||
protected void a(ISourceBlock isourceblock) {
|
||||
isourceblock.getWorld().triggerEffect(this.b ? 1000 : 1001, isourceblock.getBlockPosition(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class a extends DispenseBehaviorItem {
|
||||
|
||||
private final DispenseBehaviorItem b = new DispenseBehaviorItem();
|
||||
private final EntityBoat.EnumBoatType c;
|
||||
|
||||
public a(EntityBoat.EnumBoatType entityboat_enumboattype) {
|
||||
this.c = entityboat_enumboattype;
|
||||
}
|
||||
|
||||
public ItemStack b(ISourceBlock isourceblock, ItemStack itemstack) {
|
||||
EnumDirection enumdirection = (EnumDirection) isourceblock.e().get(BlockDispenser.FACING);
|
||||
World world = isourceblock.getWorld();
|
||||
double d0 = isourceblock.getX() + (double) ((float) enumdirection.getAdjacentX() * 1.125F);
|
||||
double d1 = isourceblock.getY() + (double) ((float) enumdirection.getAdjacentY() * 1.125F);
|
||||
double d2 = isourceblock.getZ() + (double) ((float) enumdirection.getAdjacentZ() * 1.125F);
|
||||
BlockPosition blockposition = isourceblock.getBlockPosition().shift(enumdirection);
|
||||
Material material = world.getType(blockposition).getMaterial();
|
||||
double d3;
|
||||
|
||||
if (Material.WATER.equals(material)) {
|
||||
d3 = 1.0D;
|
||||
} else {
|
||||
if (!Material.AIR.equals(material) || !Material.WATER.equals(world.getType(blockposition.down()).getMaterial())) {
|
||||
return this.b.a(isourceblock, itemstack);
|
||||
}
|
||||
|
||||
d3 = 0.0D;
|
||||
}
|
||||
|
||||
// EntityBoat entityboat = new EntityBoat(world, d0, d1 + d3, d2);
|
||||
// CraftBukkit start
|
||||
ItemStack itemstack1 = itemstack.cloneAndSubtract(1);
|
||||
org.bukkit.block.Block block = world.getWorld().getBlockAt(isourceblock.getBlockPosition().getX(), isourceblock.getBlockPosition().getY(), isourceblock.getBlockPosition().getZ());
|
||||
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
|
||||
|
||||
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2));
|
||||
if (!BlockDispenser.eventFired) {
|
||||
world.getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
|
||||
if (event.isCancelled()) {
|
||||
itemstack.add(1);
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
if (!event.getItem().equals(craftItem)) {
|
||||
itemstack.add(1);
|
||||
// Chain to handler for new item
|
||||
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
|
||||
IDispenseBehavior idispensebehavior = (IDispenseBehavior) BlockDispenser.REGISTRY.get(eventStack.getItem());
|
||||
if (idispensebehavior != IDispenseBehavior.NONE && idispensebehavior != this) {
|
||||
idispensebehavior.a(isourceblock, eventStack);
|
||||
return itemstack;
|
||||
}
|
||||
}
|
||||
|
||||
EntityBoat entityboat = new EntityBoat(world, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
|
||||
// CraftBukkit end
|
||||
|
||||
entityboat.setType(this.c);
|
||||
entityboat.yaw = enumdirection.l();
|
||||
if (!world.addEntity(entityboat)) itemstack.add(1); // CraftBukkit
|
||||
// itemstack.subtract(1); // CraftBukkit - handled during event processing
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
protected void a(ISourceBlock isourceblock) {
|
||||
isourceblock.getWorld().triggerEffect(1000, isourceblock.getBlockPosition(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,8 @@ import java.util.Random;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Expose private members<br>
|
||||
* @author cakoyo
|
||||
* Akarin Changes Note
|
||||
* 1) Expose private members (cause mixin errors)
|
||||
*/
|
||||
public class EnchantmentManager {
|
||||
|
||||
@@ -120,6 +118,7 @@ public class EnchantmentManager {
|
||||
EnchantmentManager.a.a = 0;
|
||||
EnchantmentManager.a.b = damagesource;
|
||||
a(EnchantmentManager.a, iterable);
|
||||
EnchantmentManager.a.b = null; // Reaper - Fix MC-128547
|
||||
return EnchantmentManager.a.a;
|
||||
}
|
||||
|
||||
@@ -146,6 +145,11 @@ public class EnchantmentManager {
|
||||
if (entity instanceof EntityHuman) {
|
||||
a(EnchantmentManager.c, entityliving.getItemInMainHand());
|
||||
}
|
||||
|
||||
// Reaper start - Fix MC-128547
|
||||
EnchantmentManager.c.b = null;
|
||||
EnchantmentManager.c.a = null;
|
||||
// Reaper end
|
||||
|
||||
}
|
||||
|
||||
@@ -159,7 +163,10 @@ public class EnchantmentManager {
|
||||
if (entityliving instanceof EntityHuman) {
|
||||
a(EnchantmentManager.d, entityliving.getItemInMainHand());
|
||||
}
|
||||
|
||||
// Reaper start - Fix MC-128547
|
||||
EnchantmentManager.d.b = null;
|
||||
EnchantmentManager.d.a = null;
|
||||
// Reaper end
|
||||
}
|
||||
|
||||
public static int a(Enchantment enchantment, EntityLiving entityliving) {
|
||||
|
||||
2985
sources/src/main/java/net/minecraft/server/Entity.java
Normal file
2985
sources/src/main/java/net/minecraft/server/Entity.java
Normal file
File diff suppressed because it is too large
Load Diff
476
sources/src/main/java/net/minecraft/server/EntityEnderman.java
Normal file
476
sources/src/main/java/net/minecraft/server/EntityEnderman.java
Normal file
@@ -0,0 +1,476 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.destroystokyo.paper.event.entity.EndermanEscapeEvent;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.bukkit.event.entity.EntityTargetEvent;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class EntityEnderman extends EntityMonster {
|
||||
|
||||
private static final UUID a = UUID.fromString("020E0DFB-87AE-4653-9556-831010E291A0");
|
||||
private static final AttributeModifier b = (new AttributeModifier(EntityEnderman.a, "Attacking speed boost", 0.15000000596046448D, 0)).a(false);
|
||||
private static final Set<Block> c = Sets.newIdentityHashSet();
|
||||
private static final DataWatcherObject<Optional<IBlockData>> bx = DataWatcher.a(EntityEnderman.class, DataWatcherRegistry.g);
|
||||
private static final DataWatcherObject<Boolean> by = DataWatcher.a(EntityEnderman.class, DataWatcherRegistry.h);
|
||||
private int bz;
|
||||
private int bA;
|
||||
|
||||
public EntityEnderman(World world) {
|
||||
super(world);
|
||||
this.setSize(0.6F, 2.9F);
|
||||
this.P = 1.0F;
|
||||
this.a(PathType.WATER, -1.0F);
|
||||
}
|
||||
|
||||
protected void r() {
|
||||
this.goalSelector.a(0, new PathfinderGoalFloat(this));
|
||||
this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false));
|
||||
this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D, 0.0F));
|
||||
this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
|
||||
this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
|
||||
this.goalSelector.a(10, new EntityEnderman.PathfinderGoalEndermanPlaceBlock(this));
|
||||
this.goalSelector.a(11, new EntityEnderman.PathfinderGoalEndermanPickupBlock(this));
|
||||
this.targetSelector.a(1, new EntityEnderman.PathfinderGoalPlayerWhoLookedAtTarget(this));
|
||||
this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, false, new Class[0]));
|
||||
this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget(this, EntityEndermite.class, 10, true, false, new Predicate() {
|
||||
public boolean a(@Nullable EntityEndermite entityendermite) {
|
||||
return entityendermite.p();
|
||||
}
|
||||
|
||||
public boolean apply(@Nullable Object object) {
|
||||
return this.a((EntityEndermite) object);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected void initAttributes() {
|
||||
super.initAttributes();
|
||||
this.getAttributeInstance(GenericAttributes.maxHealth).setValue(40.0D);
|
||||
this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.30000001192092896D);
|
||||
this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(7.0D);
|
||||
this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(64.0D);
|
||||
}
|
||||
|
||||
public void setGoalTarget(@Nullable EntityLiving entityliving) {
|
||||
// CraftBukkit start - fire event
|
||||
setGoalTarget(entityliving, EntityTargetEvent.TargetReason.UNKNOWN, true);
|
||||
}
|
||||
|
||||
// Paper start
|
||||
private boolean tryEscape(EndermanEscapeEvent.Reason reason) {
|
||||
return new EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent();
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@Override
|
||||
public boolean setGoalTarget(EntityLiving entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) {
|
||||
if (!super.setGoalTarget(entityliving, reason, fireEvent)) {
|
||||
return false;
|
||||
}
|
||||
entityliving = getGoalTarget();
|
||||
// CraftBukkit end
|
||||
AttributeInstance attributeinstance = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED);
|
||||
|
||||
if (entityliving == null) {
|
||||
this.bA = 0;
|
||||
this.datawatcher.set(EntityEnderman.by, Boolean.valueOf(false));
|
||||
attributeinstance.c(EntityEnderman.b);
|
||||
} else {
|
||||
this.bA = this.ticksLived;
|
||||
this.datawatcher.set(EntityEnderman.by, Boolean.valueOf(true));
|
||||
if (!attributeinstance.a(EntityEnderman.b)) {
|
||||
attributeinstance.b(EntityEnderman.b);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
protected void i() {
|
||||
super.i();
|
||||
this.datawatcher.register(EntityEnderman.bx, Optional.absent());
|
||||
this.datawatcher.register(EntityEnderman.by, Boolean.valueOf(false));
|
||||
}
|
||||
|
||||
public void p() {
|
||||
if (this.ticksLived >= this.bz + 400) {
|
||||
this.bz = this.ticksLived;
|
||||
if (!this.isSilent()) {
|
||||
this.world.a(this.locX, this.locY + (double) this.getHeadHeight(), this.locZ, SoundEffects.bh, this.bK(), 2.5F, 1.0F, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(DataWatcherObject<?> datawatcherobject) {
|
||||
if (EntityEnderman.by.equals(datawatcherobject) && this.do_() && this.world.isClientSide) {
|
||||
this.p();
|
||||
}
|
||||
|
||||
super.a(datawatcherobject);
|
||||
}
|
||||
|
||||
public static void a(DataConverterManager dataconvertermanager) {
|
||||
EntityInsentient.a(dataconvertermanager, EntityEnderman.class);
|
||||
}
|
||||
|
||||
public void b(NBTTagCompound nbttagcompound) {
|
||||
super.b(nbttagcompound);
|
||||
IBlockData iblockdata = this.getCarried();
|
||||
|
||||
if (iblockdata != null) {
|
||||
nbttagcompound.setShort("carried", (short) Block.getId(iblockdata.getBlock()));
|
||||
nbttagcompound.setShort("carriedData", (short) iblockdata.getBlock().toLegacyData(iblockdata));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(NBTTagCompound nbttagcompound) {
|
||||
super.a(nbttagcompound);
|
||||
IBlockData iblockdata;
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("carried", 8)) {
|
||||
iblockdata = Block.getByName(nbttagcompound.getString("carried")).fromLegacyData(nbttagcompound.getShort("carriedData") & '\uffff');
|
||||
} else {
|
||||
iblockdata = Block.getById(nbttagcompound.getShort("carried")).fromLegacyData(nbttagcompound.getShort("carriedData") & '\uffff');
|
||||
}
|
||||
|
||||
if (iblockdata == null || iblockdata.getBlock() == null || iblockdata.getMaterial() == Material.AIR) {
|
||||
iblockdata = null;
|
||||
}
|
||||
|
||||
this.setCarried(iblockdata);
|
||||
}
|
||||
|
||||
// Paper start - OBFHELPER - ok not really, but verify this on updates
|
||||
private boolean f(EntityHuman entityhuman) {
|
||||
boolean shouldAttack = f_real(entityhuman);
|
||||
com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) entityhuman.getBukkitEntity());
|
||||
event.setCancelled(!shouldAttack);
|
||||
return event.callEvent();
|
||||
}
|
||||
private boolean f_real(EntityHuman entityhuman) {
|
||||
// Paper end
|
||||
ItemStack itemstack = (ItemStack) entityhuman.inventory.armor.get(3);
|
||||
|
||||
if (itemstack.getItem() == Item.getItemOf(Blocks.PUMPKIN)) {
|
||||
return false;
|
||||
} else {
|
||||
Vec3D vec3d = entityhuman.e(1.0F).a();
|
||||
Vec3D vec3d1 = new Vec3D(this.locX - entityhuman.locX, this.getBoundingBox().b + (double) this.getHeadHeight() - (entityhuman.locY + (double) entityhuman.getHeadHeight()), this.locZ - entityhuman.locZ);
|
||||
double d0 = vec3d1.b();
|
||||
|
||||
vec3d1 = vec3d1.a();
|
||||
double d1 = vec3d.b(vec3d1);
|
||||
|
||||
return d1 > 1.0D - 0.025D / d0 ? entityhuman.hasLineOfSight(this) : false;
|
||||
}
|
||||
}
|
||||
|
||||
public float getHeadHeight() {
|
||||
return 2.55F;
|
||||
}
|
||||
|
||||
public void n() {
|
||||
if (this.world.isClientSide) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
this.world.addParticle(EnumParticle.PORTAL, this.locX + (this.random.nextDouble() - 0.5D) * (double) this.width, this.locY + this.random.nextDouble() * (double) this.length - 0.25D, this.locZ + (this.random.nextDouble() - 0.5D) * (double) this.width, (this.random.nextDouble() - 0.5D) * 2.0D, -this.random.nextDouble(), (this.random.nextDouble() - 0.5D) * 2.0D, new int[0]);
|
||||
}
|
||||
}
|
||||
|
||||
this.bd = false;
|
||||
super.n();
|
||||
}
|
||||
|
||||
protected void M() {
|
||||
if (this.an()) {
|
||||
this.damageEntity(DamageSource.DROWN, 1.0F);
|
||||
}
|
||||
|
||||
if (this.world.D() && this.ticksLived >= this.bA + 600) {
|
||||
float f = this.aw();
|
||||
|
||||
if (f > 0.5F && this.world.h(new BlockPosition(this)) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && tryEscape(EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper
|
||||
this.setGoalTarget((EntityLiving) null);
|
||||
this.dm();
|
||||
}
|
||||
}
|
||||
|
||||
super.M();
|
||||
}
|
||||
|
||||
public boolean teleportRandomly() { return dm(); } // Paper - OBFHELPER
|
||||
protected boolean dm() {
|
||||
double d0 = this.locX + (this.random.nextDouble() - 0.5D) * 64.0D;
|
||||
double d1 = this.locY + (double) (this.random.nextInt(64) - 32);
|
||||
double d2 = this.locZ + (this.random.nextDouble() - 0.5D) * 64.0D;
|
||||
|
||||
return this.k(d0, d1, d2);
|
||||
}
|
||||
|
||||
protected boolean a(Entity entity) {
|
||||
Vec3D vec3d = new Vec3D(this.locX - entity.locX, this.getBoundingBox().b + (double) (this.length / 2.0F) - entity.locY + (double) entity.getHeadHeight(), this.locZ - entity.locZ);
|
||||
|
||||
vec3d = vec3d.a();
|
||||
double d0 = 16.0D;
|
||||
double d1 = this.locX + (this.random.nextDouble() - 0.5D) * 8.0D - vec3d.x * 16.0D;
|
||||
double d2 = this.locY + (double) (this.random.nextInt(16) - 8) - vec3d.y * 16.0D;
|
||||
double d3 = this.locZ + (this.random.nextDouble() - 0.5D) * 8.0D - vec3d.z * 16.0D;
|
||||
|
||||
return this.k(d1, d2, d3);
|
||||
}
|
||||
|
||||
private boolean k(double d0, double d1, double d2) {
|
||||
boolean flag = this.j(d0, d1, d2);
|
||||
|
||||
if (flag) {
|
||||
this.world.a((EntityHuman) null, this.lastX, this.lastY, this.lastZ, SoundEffects.bi, this.bK(), 1.0F, 1.0F);
|
||||
this.a(SoundEffects.bi, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
protected SoundEffect F() {
|
||||
return this.do_() ? SoundEffects.bg : SoundEffects.bd;
|
||||
}
|
||||
|
||||
protected SoundEffect d(DamageSource damagesource) {
|
||||
return SoundEffects.bf;
|
||||
}
|
||||
|
||||
protected SoundEffect cf() {
|
||||
return SoundEffects.be;
|
||||
}
|
||||
|
||||
protected void dropEquipment(boolean flag, int i) {
|
||||
super.dropEquipment(flag, i);
|
||||
IBlockData iblockdata = this.getCarried();
|
||||
|
||||
if (iblockdata != null) {
|
||||
Item item = Item.getItemOf(iblockdata.getBlock());
|
||||
int j = item.k() ? iblockdata.getBlock().toLegacyData(iblockdata) : 0;
|
||||
|
||||
this.a(new ItemStack(item, 1, j), 0.0F);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected MinecraftKey J() {
|
||||
return LootTables.w;
|
||||
}
|
||||
|
||||
public void setCarried(@Nullable IBlockData iblockdata) {
|
||||
this.datawatcher.set(EntityEnderman.bx, Optional.fromNullable(iblockdata));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IBlockData getCarried() {
|
||||
return (IBlockData) ((Optional) this.datawatcher.get(EntityEnderman.bx)).orNull();
|
||||
}
|
||||
|
||||
public boolean damageEntity(DamageSource damagesource, float f) {
|
||||
if (this.isInvulnerable(damagesource)) {
|
||||
return false;
|
||||
} else if (damagesource instanceof EntityDamageSourceIndirect && tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
if (this.dm()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
boolean flag = super.damageEntity(damagesource, f);
|
||||
|
||||
if (damagesource.ignoresArmor() && this.random.nextInt(10) != 0 && tryEscape(damagesource == DamageSource.DROWN ? EndermanEscapeEvent.Reason.DROWN : EndermanEscapeEvent.Reason.CRITICAL_HIT)) { // Paper
|
||||
this.dm();
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean do_() {
|
||||
return ((Boolean) this.datawatcher.get(EntityEnderman.by)).booleanValue();
|
||||
}
|
||||
|
||||
static {
|
||||
EntityEnderman.c.add(Blocks.GRASS);
|
||||
EntityEnderman.c.add(Blocks.DIRT);
|
||||
EntityEnderman.c.add(Blocks.SAND);
|
||||
EntityEnderman.c.add(Blocks.GRAVEL);
|
||||
EntityEnderman.c.add(Blocks.YELLOW_FLOWER);
|
||||
EntityEnderman.c.add(Blocks.RED_FLOWER);
|
||||
EntityEnderman.c.add(Blocks.BROWN_MUSHROOM);
|
||||
EntityEnderman.c.add(Blocks.RED_MUSHROOM);
|
||||
EntityEnderman.c.add(Blocks.TNT);
|
||||
EntityEnderman.c.add(Blocks.CACTUS);
|
||||
EntityEnderman.c.add(Blocks.CLAY);
|
||||
EntityEnderman.c.add(Blocks.PUMPKIN);
|
||||
EntityEnderman.c.add(Blocks.MELON_BLOCK);
|
||||
EntityEnderman.c.add(Blocks.MYCELIUM);
|
||||
EntityEnderman.c.add(Blocks.NETHERRACK);
|
||||
}
|
||||
|
||||
static class PathfinderGoalEndermanPickupBlock extends PathfinderGoal {
|
||||
|
||||
private final EntityEnderman enderman;
|
||||
|
||||
public PathfinderGoalEndermanPickupBlock(EntityEnderman entityenderman) {
|
||||
this.enderman = entityenderman;
|
||||
}
|
||||
|
||||
public boolean a() {
|
||||
return this.enderman.getCarried() != null ? false : (!this.enderman.world.getGameRules().getBoolean("mobGriefing") ? false : this.enderman.getRandom().nextInt(20) == 0);
|
||||
}
|
||||
|
||||
public void e() {
|
||||
Random random = this.enderman.getRandom();
|
||||
World world = this.enderman.world;
|
||||
int i = MathHelper.floor(this.enderman.locX - 2.0D + random.nextDouble() * 4.0D);
|
||||
int j = MathHelper.floor(this.enderman.locY + random.nextDouble() * 3.0D);
|
||||
int k = MathHelper.floor(this.enderman.locZ - 2.0D + random.nextDouble() * 4.0D);
|
||||
BlockPosition blockposition = new BlockPosition(i, j, k);
|
||||
IBlockData iblockdata = world.getTypeIfLoaded(blockposition); // NeonPaper
|
||||
if (iblockdata == null) return; // NeonPaper
|
||||
Block block = iblockdata.getBlock();
|
||||
MovingObjectPosition movingobjectposition = world.rayTrace(new Vec3D((double) ((float) MathHelper.floor(this.enderman.locX) + 0.5F), (double) ((float) j + 0.5F), (double) ((float) MathHelper.floor(this.enderman.locZ) + 0.5F)), new Vec3D((double) ((float) i + 0.5F), (double) ((float) j + 0.5F), (double) ((float) k + 0.5F)), false, true, false);
|
||||
boolean flag = movingobjectposition != null && movingobjectposition.a().equals(blockposition);
|
||||
|
||||
if (EntityEnderman.c.contains(block) && flag) {
|
||||
// CraftBukkit start - Pickup event
|
||||
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, this.enderman.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), org.bukkit.Material.AIR).isCancelled()) {
|
||||
this.enderman.setCarried(iblockdata);
|
||||
world.setAir(blockposition);
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class PathfinderGoalEndermanPlaceBlock extends PathfinderGoal {
|
||||
|
||||
private final EntityEnderman a;
|
||||
|
||||
public PathfinderGoalEndermanPlaceBlock(EntityEnderman entityenderman) {
|
||||
this.a = entityenderman;
|
||||
}
|
||||
|
||||
public boolean a() {
|
||||
return this.a.getCarried() == null ? false : (!this.a.world.getGameRules().getBoolean("mobGriefing") ? false : this.a.getRandom().nextInt(2000) == 0);
|
||||
}
|
||||
|
||||
public void e() {
|
||||
Random random = this.a.getRandom();
|
||||
World world = this.a.world;
|
||||
int i = MathHelper.floor(this.a.locX - 1.0D + random.nextDouble() * 2.0D);
|
||||
int j = MathHelper.floor(this.a.locY + random.nextDouble() * 2.0D);
|
||||
int k = MathHelper.floor(this.a.locZ - 1.0D + random.nextDouble() * 2.0D);
|
||||
BlockPosition blockposition = new BlockPosition(i, j, k);
|
||||
IBlockData iblockdata = world.getTypeIfLoaded(blockposition); // NeonPaper
|
||||
if (iblockdata == null) return; // NeonPaper
|
||||
IBlockData iblockdata1 = world.getType(blockposition.down());
|
||||
IBlockData iblockdata2 = this.a.getCarried();
|
||||
|
||||
if (iblockdata2 != null && this.a(world, blockposition, iblockdata2.getBlock(), iblockdata, iblockdata1)) {
|
||||
// CraftBukkit start - Place event
|
||||
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.a, blockposition, this.a.getCarried().getBlock(), this.a.getCarried().getBlock().toLegacyData(this.a.getCarried())).isCancelled()) {
|
||||
world.setTypeAndData(blockposition, iblockdata2, 3);
|
||||
this.a.setCarried((IBlockData) null);
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean a(World world, BlockPosition blockposition, Block block, IBlockData iblockdata, IBlockData iblockdata1) {
|
||||
return !block.canPlace(world, blockposition) ? false : (iblockdata.getMaterial() != Material.AIR ? false : (iblockdata1.getMaterial() == Material.AIR ? false : iblockdata1.g()));
|
||||
}
|
||||
}
|
||||
|
||||
static class PathfinderGoalPlayerWhoLookedAtTarget extends PathfinderGoalNearestAttackableTarget<EntityHuman> {
|
||||
|
||||
private final EntityEnderman i; public EntityEnderman getEnderman() { return i; } // Paper - OBFHELPER
|
||||
private EntityHuman j;
|
||||
private int k;
|
||||
private int l;
|
||||
|
||||
public PathfinderGoalPlayerWhoLookedAtTarget(EntityEnderman entityenderman) {
|
||||
super(entityenderman, EntityHuman.class, false);
|
||||
this.i = entityenderman;
|
||||
}
|
||||
|
||||
public boolean a() {
|
||||
double d0 = this.i();
|
||||
|
||||
this.j = this.i.world.a(this.i.locX, this.i.locY, this.i.locZ, d0, d0, (Function) null, new Predicate() {
|
||||
public boolean a(@Nullable EntityHuman entityhuman) {
|
||||
return entityhuman != null && PathfinderGoalPlayerWhoLookedAtTarget.this.i.f(entityhuman);
|
||||
}
|
||||
|
||||
public boolean apply(@Nullable Object object) {
|
||||
return this.a((EntityHuman) object);
|
||||
}
|
||||
});
|
||||
return this.j != null;
|
||||
}
|
||||
|
||||
public void c() {
|
||||
this.k = 5;
|
||||
this.l = 0;
|
||||
}
|
||||
|
||||
public void d() {
|
||||
this.j = null;
|
||||
super.d();
|
||||
}
|
||||
|
||||
public boolean b() {
|
||||
if (this.j != null) {
|
||||
if (!this.i.f(this.j)) {
|
||||
return false;
|
||||
} else {
|
||||
this.i.a((Entity) this.j, 10.0F, 10.0F);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return this.d != null && ((EntityHuman) this.d).isAlive() ? true : super.b();
|
||||
}
|
||||
}
|
||||
|
||||
public void e() {
|
||||
if (this.j != null) {
|
||||
if (--this.k <= 0) {
|
||||
this.d = this.j;
|
||||
this.j = null;
|
||||
super.c();
|
||||
}
|
||||
} else {
|
||||
if (this.d != null) {
|
||||
if (this.i.f((EntityHuman) this.d)) {
|
||||
if (((EntityHuman) this.d).h(this.i) < 16.0D && this.getEnderman().tryEscape(EndermanEscapeEvent.Reason.STARE)) { // Paper
|
||||
this.i.dm();
|
||||
}
|
||||
|
||||
this.l = 0;
|
||||
} else if (((EntityHuman) this.d).h(this.i) > 256.0D && this.l++ >= 30 && this.i.a((Entity) this.d)) {
|
||||
this.l = 0;
|
||||
}
|
||||
}
|
||||
|
||||
super.e();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user