Compare commits
217 Commits
1.12.2-R0.
...
ver/1.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b5de991da | ||
|
|
2ce7ad3504 | ||
|
|
458d25a6ff | ||
|
|
7c02d15374 | ||
|
|
5b84e0b4ae | ||
|
|
521961be73 | ||
|
|
6f58c7158d | ||
|
|
1356bf841f | ||
|
|
48a23b9e63 | ||
|
|
c063f92ff1 | ||
|
|
2c18d26438 | ||
|
|
3b949e82ba | ||
|
|
49c3060432 | ||
|
|
88415e09b5 | ||
|
|
0a7f790e78 | ||
|
|
ef0ce05cae | ||
|
|
2593ab8eba | ||
|
|
732cc57ee5 | ||
|
|
2f867dd364 | ||
|
|
75ab6a414c | ||
|
|
4eac44c8df | ||
|
|
0c3fec2c10 | ||
|
|
46e8595949 | ||
|
|
59b1567405 | ||
|
|
f8e01fa0f9 | ||
|
|
1c5da01dec | ||
|
|
916c298fbf | ||
|
|
72f3d31652 | ||
|
|
64f2e7dcd1 | ||
|
|
2695e3551f | ||
|
|
c16c1ef11c | ||
|
|
687b8369a6 | ||
|
|
779148ddc1 | ||
|
|
d4f5420183 | ||
|
|
b4fa26e0c1 | ||
|
|
90adbd71c7 | ||
|
|
9f7d36ff50 | ||
|
|
6dc0a094df | ||
|
|
5005a19195 | ||
|
|
a64ee6fa0d | ||
|
|
67817f6ba2 | ||
|
|
7eea320298 | ||
|
|
0edce075eb | ||
|
|
042c7b3301 | ||
|
|
a7b15975df | ||
|
|
5d8221f7aa | ||
|
|
598d8ed104 | ||
|
|
56066ac93e | ||
|
|
eac7e704f1 | ||
|
|
e45247fab4 | ||
|
|
996c3e4949 | ||
|
|
48b9fea5af | ||
|
|
7e6de94cec | ||
|
|
563d3973c1 | ||
|
|
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 | ||
|
|
b4f557ccb1 | ||
|
|
5f936eb1b7 | ||
|
|
f46e63958b | ||
|
|
68833e6726 | ||
|
|
3a1e04cfbb | ||
|
|
0f00949e6f | ||
|
|
24d0c6a541 | ||
|
|
474d9ecb6a | ||
|
|
2282ca02b7 | ||
|
|
ab9aaa6195 | ||
|
|
f89f26e2db | ||
|
|
7bdc57cb70 | ||
|
|
b4da072470 | ||
|
|
c43b8592c9 | ||
|
|
a17a194c6a | ||
|
|
7856043ed6 | ||
|
|
69e6cc1fea | ||
|
|
8a3ddb716e | ||
|
|
ccb3bb9865 | ||
|
|
f0df6a62c4 | ||
|
|
16f172a4d1 | ||
|
|
1bd0498f86 | ||
|
|
5a90c8d8fd |
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
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
||||
[submodule "work/Paper"]
|
||||
path = work/Paper
|
||||
url = https://github.com/Akarin-project/Paper.git
|
||||
url = https://github.com/Akarin-project/Paper.git
|
||||
branch = pre/1.13
|
||||
@@ -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,33 @@
|
||||
# <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)
|
||||
[](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.13)
|
||||
|
||||
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**](https://jenkins.bennydoesstuff.me/view/Akarin/) - Kudos to [BennyDoesStuff](https://github.com/BennyDoesTheStuff)
|
||||
+ [**Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.13) - Checkout the 'Artifacts' tab of the latest build
|
||||
|
||||
*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 +38,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
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.destroystokyo.paper.antixray;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.DataPalette;
|
||||
import net.minecraft.server.PacketPlayOutMapChunk;
|
||||
|
||||
public class ChunkPacketInfo<T> {
|
||||
|
||||
private final PacketPlayOutMapChunk packetPlayOutMapChunk;
|
||||
private final Chunk chunk;
|
||||
private final int chunkSectionSelector;
|
||||
private ByteBuf data; // Akarin
|
||||
private final int[] bitsPerObject = new int[16];
|
||||
private final Object[] dataPalettes = new Object[16];
|
||||
private final int[] dataBitsIndexes = new int[16];
|
||||
private final Object[][] predefinedObjects = new Object[16][];
|
||||
|
||||
public ChunkPacketInfo(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 getBitsPerObject(int chunkSectionIndex) {
|
||||
return bitsPerObject[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setBitsPerObject(int chunkSectionIndex, int bitsPerObject) {
|
||||
this.bitsPerObject[chunkSectionIndex] = bitsPerObject;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public DataPalette<T> getDataPalette(int chunkSectionIndex) {
|
||||
return (DataPalette<T>) dataPalettes[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setDataPalette(int chunkSectionIndex, DataPalette<T> dataPalette) {
|
||||
dataPalettes[chunkSectionIndex] = dataPalette;
|
||||
}
|
||||
|
||||
public int getDataBitsIndex(int chunkSectionIndex) {
|
||||
return dataBitsIndexes[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setDataBitsIndex(int chunkSectionIndex, int dataBitsIndex) {
|
||||
dataBitsIndexes[chunkSectionIndex] = dataBitsIndex;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T[] getPredefinedObjects(int chunkSectionIndex) {
|
||||
return (T[]) predefinedObjects[chunkSectionIndex];
|
||||
}
|
||||
|
||||
public void setPredefinedObjects(int chunkSectionIndex, T[] predefinedObjects) {
|
||||
this.predefinedObjects[chunkSectionIndex] = predefinedObjects;
|
||||
}
|
||||
|
||||
public boolean isWritten(int chunkSectionIndex) {
|
||||
return bitsPerObject[chunkSectionIndex] != 0;
|
||||
}
|
||||
}
|
||||
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 bitsPerObject;
|
||||
private int mask;
|
||||
private int longInDataBitsIndex;
|
||||
private int bitInLongIndex;
|
||||
private long current;
|
||||
|
||||
public void setDataBits(ByteBuf dataBits) { // Akarin
|
||||
this.dataBits = dataBits;
|
||||
}
|
||||
|
||||
public void setBitsPerObject(int bitsPerObject) {
|
||||
this.bitsPerObject = bitsPerObject;
|
||||
mask = (1 << bitsPerObject) - 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 += bitsPerObject;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
|
||||
if (bitInLongIndex > 0) {
|
||||
value |= current << bitsPerObject - 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 bitsPerObject;
|
||||
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 setBitsPerObject(int bitsPerObject) {
|
||||
this.bitsPerObject = bitsPerObject;
|
||||
mask = (1 << bitsPerObject) - 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 += bitsPerObject;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
finish();
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
|
||||
if (bitInLongIndex > 0) {
|
||||
current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void skip() {
|
||||
bitInLongIndex += bitsPerObject;
|
||||
|
||||
if (bitInLongIndex > 63) {
|
||||
finish();
|
||||
bitInLongIndex -= 64;
|
||||
longInDataBitsIndex += 8;
|
||||
init();
|
||||
}
|
||||
}
|
||||
}
|
||||
198
removed/net/minecraft/server/PacketPlayOutMapChunk.java
Normal file
198
removed/net/minecraft/server/PacketPlayOutMapChunk.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* 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; private ByteBuf getData() { return this.d; } // Paper - OBFHELPER // 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) {
|
||||
ChunkPacketInfo<IBlockData> chunkPacketInfo = chunk.world.chunkPacketBlockController.getChunkPacketInfo(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.g();
|
||||
|
||||
this.d = allocateBuffer(this.a(chunk, flag, i)); // Akarin
|
||||
|
||||
// Paper start - Anti-Xray - Add chunk packet info
|
||||
if (chunkPacketInfo != null) {
|
||||
chunkPacketInfo.setData(this.getData());
|
||||
}
|
||||
// Paper end
|
||||
|
||||
this.c = this.writeChunk(new PacketDataSerializer(this.getData()), chunk, flag, i, chunkPacketInfo); // 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.f() || (i & 1 << j) != 0) {
|
||||
NBTTagCompound nbttagcompound = tileentity.aa_();
|
||||
if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper
|
||||
|
||||
this.e.add(nbttagcompound);
|
||||
}
|
||||
}
|
||||
|
||||
chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // 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.getData().capacity()); // Akarin
|
||||
packetdataserializer.writeBytes(this.getData().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 h() { 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 methods
|
||||
public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, boolean writeSkyLightArray, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector); }
|
||||
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, ChunkPacketInfo<IBlockData> chunkPacketInfo) { return this.a(packetDataSerializer, chunk, writeSkyLightArray, chunkSectionSelector, chunkPacketInfo); } // Paper - OBFHELPER // Paper - Anti-Xray - Add chunk packet info
|
||||
public int a(PacketDataSerializer packetdataserializer, Chunk chunk, boolean flag, int i, ChunkPacketInfo<IBlockData> chunkPacketInfo) { // Paper - Anti-Xray - Add chunk packet info
|
||||
int j = 0;
|
||||
ChunkSection[] achunksection = chunk.getSections();
|
||||
int k = 0;
|
||||
|
||||
int l;
|
||||
|
||||
for (l = achunksection.length; k < l; ++k) {
|
||||
ChunkSection chunksection = achunksection[k];
|
||||
|
||||
if (chunksection != Chunk.a && (!this.f() || !chunksection.a()) && (i & 1 << k) != 0) {
|
||||
j |= 1 << k;
|
||||
chunksection.getBlocks().writeDataPaletteBlock(packetdataserializer, chunkPacketInfo, k); // Paper - Anti-Xray - Add chunk packet info
|
||||
packetdataserializer.writeBytes(chunksection.getEmittedLightArray().asBytes());
|
||||
if (flag) {
|
||||
packetdataserializer.writeBytes(chunksection.getSkyLightArray().asBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.f()) {
|
||||
BiomeBase[] abiomebase = chunk.getBiomeIndex();
|
||||
|
||||
for (l = 0; l < abiomebase.length; ++l) {
|
||||
packetdataserializer.writeInt(IRegistry.BIOME.a(abiomebase[l])); // Paper - decompile fix
|
||||
}
|
||||
}
|
||||
|
||||
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.f() || !chunksection.a()) && (i & 1 << k) != 0) {
|
||||
j += chunksection.getBlocks().a();
|
||||
j += chunksection.getEmittedLightArray().asBytes().length;
|
||||
if (flag) {
|
||||
j += chunksection.getSkyLightArray().asBytes().length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.f()) {
|
||||
j += chunk.getBiomeIndex().length * 4;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
public boolean f() {
|
||||
return this.f;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
set -e
|
||||
basedir="$(cd "$1" && pwd -P)"
|
||||
workdir="$basedir/work"
|
||||
version="ver/1.13"
|
||||
paperbasedir="$basedir/work/Paper"
|
||||
paperworkdir="$basedir/work/Paper/work"
|
||||
|
||||
@@ -16,24 +17,32 @@ if [ "$2" == "--setup" ] || [ "$3" == "--setup" ] || [ "$4" == "--setup" ]; then
|
||||
rm Minecraft/ -r
|
||||
fi
|
||||
git clone https://github.com/Akarin-project/Minecraft.git
|
||||
cd "Minecraft" && git checkout "$version"
|
||||
fi
|
||||
|
||||
cd "$paperbasedir"
|
||||
./paper patch
|
||||
./paper jar
|
||||
)
|
||||
fi
|
||||
|
||||
echo "[Akarin] Ready to build"
|
||||
(
|
||||
cd "$paperworkdir/BuildData" && git checkout "$version"
|
||||
cd "$paperbasedir"
|
||||
echo "[Akarin] Touch sources.."
|
||||
\cp -rf "$basedir/sources/src" "$paperbasedir/Paper-Server/"
|
||||
\cp -rf "$basedir/sources/pom.xml" "$paperbasedir/Paper-Server/"
|
||||
|
||||
cd "$paperbasedir"
|
||||
if [ "$2" == "--fast" ] || [ "$3" == "--fast" ] || [ "$4" == "--fast" ]; then
|
||||
echo "[Akarin] Test has been skipped"
|
||||
\cp -rf "$basedir/sources/src" "$paperbasedir/Paper-Server/"
|
||||
\cp -rf "$basedir/sources/pom.xml" "$paperbasedir/Paper-Server/"
|
||||
mvn clean install -DskipTests
|
||||
else
|
||||
else
|
||||
rm -rf Paper-API/src
|
||||
rm -rf Paper-Server/src
|
||||
./paper patch
|
||||
\cp -rf "$basedir/sources/src" "$paperbasedir/Paper-Server/"
|
||||
\cp -rf "$basedir/sources/pom.xml" "$paperbasedir/Paper-Server/"
|
||||
mvn clean install
|
||||
fi
|
||||
|
||||
|
||||
5
scripts/inst.sh
Normal file → Executable file
5
scripts/inst.sh
Normal file → Executable file
@@ -2,9 +2,10 @@
|
||||
|
||||
(
|
||||
set -e
|
||||
basedir="$pwd"
|
||||
basedir="$(pwd -P)"
|
||||
version="pre/1.13"
|
||||
|
||||
(git submodule update --init --remote && chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
|
||||
(git submodule update --init --remote && cd "work/Paper" && git checkout "$version" && cd "$basedir" && chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
|
||||
echo "Failed to build Akarin"
|
||||
exit 1
|
||||
) || exit 1
|
||||
|
||||
136
sources/pom.xml
136
sources/pom.xml
@@ -3,22 +3,22 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>akarin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.12.2-R0.1-RELEASE</version>
|
||||
<version>1.13.1-R0.1-SNAPSHOT</version>
|
||||
<name>Akarin</name>
|
||||
<url>https://github.com/Akarin-project/Akarin</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<api.version>1.12.2-R0.1-SNAPSHOT</api.version>
|
||||
<minecraft.version>1.12.2</minecraft.version>
|
||||
<minecraft_version>1_12_R1</minecraft_version>
|
||||
<api.version>1.13.1-R0.1-SNAPSHOT</api.version>
|
||||
<minecraft.version>1.13.1</minecraft.version>
|
||||
<minecraft_version>1_13_R2</minecraft_version>
|
||||
<buildtag.prefix>git-Bukkit-</buildtag.prefix>
|
||||
<buildtag.suffix></buildtag.suffix>
|
||||
<maven.build.timestamp.format>yyyyMMdd-HHmm</maven.build.timestamp.format>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
|
||||
<parent>
|
||||
<groupId>com.destroystokyo.paper</groupId>
|
||||
<artifactId>paper-parent</artifactId>
|
||||
@@ -26,13 +26,6 @@
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!-- bugfixes (netty#6607) -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.24.Final</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.destroystokyo.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
@@ -46,42 +39,22 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
<version>5.0.4</version>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>minecraft-server</artifactId>
|
||||
<version>${minecraft.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.21.0.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.45</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.trove4j</groupId>
|
||||
<artifactId>trove4j</artifactId>
|
||||
<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>
|
||||
|
||||
<!--
|
||||
Required to add the missing Log4j2Plugins.dat file from log4j-core
|
||||
which has been removed by Mojang. Without it, log4j has to classload
|
||||
@@ -94,8 +67,6 @@
|
||||
<version>2.8.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper - Add additional Log4J dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
@@ -107,7 +78,37 @@
|
||||
<artifactId>log4j-iostreams</artifactId>
|
||||
<version>2.8.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper - Async loggers -->
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>6.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.23.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.46</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.trove4j</groupId>
|
||||
<artifactId>trove4j</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- testing -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
@@ -121,33 +122,46 @@
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Akarin -->
|
||||
<dependency>
|
||||
<groupId>io.akarin</groupId>
|
||||
<artifactId>legacylauncher</artifactId>
|
||||
<version>1.20</version>
|
||||
<version>2.1</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>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.6.3-SNAPSHOT</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>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spigotmc-public</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spongepowered-repo</id>
|
||||
<url>https://repo.spongepowered.org/maven/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>nallar-repo</id>
|
||||
<url>http://repo.nallar.me/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
@@ -168,7 +182,7 @@
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<outputPrefix>git-Akarin-</outputPrefix>
|
||||
<scmDirectory>..</scmDirectory>
|
||||
<scmDirectory>../..</scmDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -189,6 +203,7 @@
|
||||
<manifestEntries>
|
||||
<Main-Class>net.minecraft.launchwrapper.Launch</Main-Class>
|
||||
<Implementation-Title>CraftBukkit</Implementation-Title>
|
||||
<!--suppress MavenModelInspection -->
|
||||
<Implementation-Version>${describe}</Implementation-Version>
|
||||
<Implementation-Vendor>${maven.build.timestamp}</Implementation-Vendor>
|
||||
<Specification-Title>Bukkit</Specification-Title>
|
||||
@@ -221,7 +236,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -265,15 +280,15 @@
|
||||
</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>
|
||||
<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>
|
||||
@@ -293,8 +308,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<!-- BUILDTOOLS-362 / SUREFIRE-1444 - newer version fails in Docker -->
|
||||
<version>2.20</version>
|
||||
<version>2.12.4</version>
|
||||
<configuration>
|
||||
<workingDirectory>${basedir}/target/test-server</workingDirectory>
|
||||
<excludes>
|
||||
@@ -318,7 +332,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||
<version>1.16</version>
|
||||
<version>1.17</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>process-classes</phase>
|
||||
|
||||
127
sources/src/main/java/co/aikar/timings/TimingData.java
Normal file
127
sources/src/main/java/co/aikar/timings/TimingData.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2014 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 java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import static co.aikar.util.JSONUtil.toArray;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Thread safe timing (safety)
|
||||
*/
|
||||
/**
|
||||
* <p>Lightweight object for tracking timing data</p>
|
||||
*
|
||||
* This is broken out to reduce memory usage
|
||||
*/
|
||||
class TimingData {
|
||||
private final int id;
|
||||
private int count = 0;
|
||||
private int lagCount = 0;
|
||||
private long totalTime = 0;
|
||||
private long lagTotalTime = 0;
|
||||
private AtomicInteger curTickCount = new AtomicInteger(); // Akarin
|
||||
private LongAdder curTickTotal = new LongAdder(); // Akarin
|
||||
|
||||
TimingData(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private TimingData(TimingData data) {
|
||||
this.id = data.id;
|
||||
this.totalTime = data.totalTime;
|
||||
this.lagTotalTime = data.lagTotalTime;
|
||||
this.count = data.count;
|
||||
this.lagCount = data.lagCount;
|
||||
}
|
||||
|
||||
void add(long diff) {
|
||||
curTickCount.incrementAndGet();
|
||||
curTickTotal.add(diff);
|
||||
}
|
||||
|
||||
void processTick(boolean violated) {
|
||||
totalTime += curTickTotal.sum();
|
||||
count += curTickCount.get();
|
||||
if (violated) {
|
||||
lagTotalTime += curTickTotal.sum();
|
||||
lagCount += curTickCount.get();
|
||||
}
|
||||
curTickTotal.reset();
|
||||
curTickCount.set(0);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count = 0;
|
||||
lagCount = 0;
|
||||
curTickTotal.reset();
|
||||
curTickCount.set(0);
|
||||
totalTime = 0;
|
||||
lagTotalTime = 0;
|
||||
}
|
||||
|
||||
protected TimingData clone() {
|
||||
return new TimingData(this);
|
||||
}
|
||||
|
||||
List<Object> export() {
|
||||
List<Object> list = toArray(
|
||||
id,
|
||||
count,
|
||||
totalTime);
|
||||
if (lagCount > 0) {
|
||||
list.add(lagCount);
|
||||
list.add(lagTotalTime);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
boolean hasData() {
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
long getTotalTime() {
|
||||
return totalTime;
|
||||
}
|
||||
|
||||
int getCurTickCount() {
|
||||
return curTickCount.get();
|
||||
}
|
||||
|
||||
void setCurTickCount(int curTickCount) {
|
||||
this.curTickCount.getAndSet(curTickCount);
|
||||
}
|
||||
|
||||
long getCurTickTotal() {
|
||||
return curTickTotal.sum();
|
||||
}
|
||||
|
||||
void setCurTickTotal(long curTickTotal) {
|
||||
this.curTickTotal.reset();
|
||||
this.curTickTotal.add(curTickTotal);
|
||||
}
|
||||
}
|
||||
@@ -24,21 +24,22 @@
|
||||
package co.aikar.timings;
|
||||
|
||||
import co.aikar.util.LoadingIntMap;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Add volatile to fields<br>
|
||||
* @author cakoyo
|
||||
* Akarin Changes Note
|
||||
* 1) Thread safe timing (safety)
|
||||
*/
|
||||
class TimingHandler implements Timing {
|
||||
|
||||
private static int idPool = 1;
|
||||
final int id = idPool++;
|
||||
private static AtomicInteger idPool = new AtomicInteger(1);
|
||||
final int id = idPool.getAndIncrement();
|
||||
|
||||
final String name;
|
||||
private final boolean verbose;
|
||||
@@ -48,12 +49,12 @@ 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 boolean added;
|
||||
private AtomicLong start = new AtomicLong(); // Akarin
|
||||
private AtomicInteger timingDepth = new AtomicInteger(); // Akarin
|
||||
private volatile boolean added; // Akarin
|
||||
private boolean timed;
|
||||
private boolean enabled;
|
||||
private TimingHandler parent;
|
||||
private volatile TimingHandler parent; // Akarin
|
||||
|
||||
TimingHandler(TimingIdentifier id) {
|
||||
if (id.name.startsWith("##")) {
|
||||
@@ -76,61 +77,51 @@ class TimingHandler implements Timing {
|
||||
}
|
||||
|
||||
void processTick(boolean violated) {
|
||||
if (timingDepth != 0 || record.getCurTickCount() == 0) {
|
||||
timingDepth = 0;
|
||||
start = 0;
|
||||
if (timingDepth.get() != 0 || record.getCurTickCount() == 0) {
|
||||
timingDepth.set(0);
|
||||
start.set(0);
|
||||
return;
|
||||
}
|
||||
|
||||
record.processTick(violated);
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
for (TimingData handler : children.values()) {
|
||||
handler.processTick(violated);
|
||||
}
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
startTiming();
|
||||
}
|
||||
startTiming();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
stopTiming();
|
||||
}
|
||||
stopTiming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTiming() {
|
||||
if (enabled && ++timingDepth == 1) {
|
||||
start = System.nanoTime();
|
||||
if (enabled && /*Bukkit.isPrimaryThread() &&*/ timingDepth.incrementAndGet() == 1) { // Akarin
|
||||
start.getAndSet(System.nanoTime());
|
||||
parent = TimingsManager.CURRENT;
|
||||
TimingsManager.CURRENT = this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopTiming() {
|
||||
if (enabled && --timingDepth == 0 && start != 0) {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||
new Throwable().printStackTrace();
|
||||
start = 0;
|
||||
return;
|
||||
}
|
||||
addDiff(System.nanoTime() - start);
|
||||
start = 0;
|
||||
if (enabled && start.get() != 0 && /*Bukkit.isPrimaryThread() &&*/ timingDepth.decrementAndGet() == 0) { // Akarin
|
||||
long prev = start.getAndSet(0); // Akarin
|
||||
addDiff(System.nanoTime() - prev); // Akarin
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
if (enabled && timingDepth > 0) {
|
||||
start = 0;
|
||||
if (enabled && timingDepth.get() > 0) {
|
||||
start.getAndSet(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,18 +129,24 @@ class TimingHandler implements Timing {
|
||||
if (TimingsManager.CURRENT == this) {
|
||||
TimingsManager.CURRENT = parent;
|
||||
if (parent != null) {
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
parent.children.get(id).add(diff);
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
}
|
||||
}
|
||||
record.add(diff);
|
||||
if (!added) {
|
||||
added = true;
|
||||
timed = true;
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
TimingsManager.HANDLERS.add(this);
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
}
|
||||
if (groupHandler != null) {
|
||||
groupHandler.addDiff(diff);
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
groupHandler.children.get(id).add(diff);
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,10 +160,12 @@ class TimingHandler implements Timing {
|
||||
if (full) {
|
||||
timed = false;
|
||||
}
|
||||
start = 0;
|
||||
timingDepth = 0;
|
||||
start.set(0);
|
||||
timingDepth.set(0);
|
||||
added = false;
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
children.clear();
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
checkEnabled();
|
||||
}
|
||||
|
||||
@@ -207,11 +206,13 @@ class TimingHandler implements Timing {
|
||||
}
|
||||
|
||||
TimingData[] cloneChildren() {
|
||||
final TimingData[] clonedChildren = new TimingData[children.size()];
|
||||
int i = 0;
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
final TimingData[] clonedChildren = new TimingData[children.size()];
|
||||
for (TimingData child : children.values()) {
|
||||
clonedChildren[i++] = child.clone();
|
||||
}
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
return clonedChildren;
|
||||
}
|
||||
}
|
||||
|
||||
355
sources/src/main/java/co/aikar/timings/TimingHistory.java
Normal file
355
sources/src/main/java/co/aikar/timings/TimingHistory.java
Normal file
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* This file is licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2014 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 co.aikar.timings.TimingHistory.RegionData.RegionId;
|
||||
import co.aikar.util.JSONUtil;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import co.aikar.util.LoadingMap;
|
||||
import co.aikar.util.MRUMapCache;
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK;
|
||||
import static co.aikar.timings.TimingsManager.MINUTE_REPORTS;
|
||||
import static co.aikar.util.JSONUtil.*;
|
||||
|
||||
@SuppressWarnings({"deprecation", "SuppressionAnnotation", "Convert2Lambda", "Anonymous2MethodRef"})
|
||||
public class TimingHistory {
|
||||
public static long lastMinuteTime;
|
||||
public static long timedTicks;
|
||||
public static long playerTicks;
|
||||
public static long entityTicks;
|
||||
public static long tileEntityTicks;
|
||||
public static long activatedEntityTicks;
|
||||
private static int worldIdPool = 1;
|
||||
static Map<String, Integer> worldMap = LoadingMap.newHashMap(new Function<String, Integer>() {
|
||||
@Override
|
||||
public Integer apply(String input) {
|
||||
return worldIdPool++;
|
||||
}
|
||||
});
|
||||
private final long endTime;
|
||||
private final long startTime;
|
||||
private final long totalTicks;
|
||||
private final long totalTime; // Represents all time spent running the server this history
|
||||
private final MinuteReport[] minuteReports;
|
||||
|
||||
private final TimingHistoryEntry[] entries;
|
||||
final Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||
final Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||
private final Map<Object, Object> worlds;
|
||||
|
||||
TimingHistory() {
|
||||
this.endTime = System.currentTimeMillis() / 1000;
|
||||
this.startTime = TimingsManager.historyStart / 1000;
|
||||
if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) {
|
||||
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]);
|
||||
this.minuteReports[this.minuteReports.length - 1] = new MinuteReport();
|
||||
} else {
|
||||
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]);
|
||||
}
|
||||
long ticks = 0;
|
||||
for (MinuteReport mp : this.minuteReports) {
|
||||
ticks += mp.ticksRecord.timed;
|
||||
}
|
||||
this.totalTicks = ticks;
|
||||
this.totalTime = FULL_SERVER_TICK.record.getTotalTime();
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()];
|
||||
|
||||
int i = 0;
|
||||
for (TimingHandler handler : TimingsManager.HANDLERS) {
|
||||
entries[i++] = new TimingHistoryEntry(handler);
|
||||
}
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
|
||||
|
||||
// Information about all loaded chunks/entities
|
||||
//noinspection unchecked
|
||||
this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function<World, JSONPair>() {
|
||||
@Override
|
||||
public JSONPair apply(World world) {
|
||||
Map<RegionId, RegionData> regions = LoadingMap.newHashMap(RegionData.LOADER);
|
||||
|
||||
for (Chunk chunk : world.getLoadedChunks()) {
|
||||
RegionData data = regions.get(new RegionId(chunk.getX(), chunk.getZ()));
|
||||
|
||||
for (Entity entity : chunk.getEntities()) {
|
||||
if (entity == null) {
|
||||
Bukkit.getLogger().warning("Null entity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
|
||||
continue;
|
||||
}
|
||||
|
||||
data.entityCounts.get(entity.getType()).increment();
|
||||
}
|
||||
|
||||
for (BlockState tileEntity : chunk.getTileEntities()) {
|
||||
if (tileEntity == null) {
|
||||
Bukkit.getLogger().warning("Null tileentity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
|
||||
continue;
|
||||
}
|
||||
|
||||
data.tileEntityCounts.get(tileEntity.getBlock().getType()).increment();
|
||||
}
|
||||
}
|
||||
return pair(
|
||||
worldMap.get(world.getName()),
|
||||
toArrayMapper(regions.values(),new Function<RegionData, Object>() {
|
||||
@Override
|
||||
public Object apply(RegionData input) {
|
||||
return toArray(
|
||||
input.regionId.x,
|
||||
input.regionId.z,
|
||||
toObjectMapper(input.entityCounts.entrySet(),
|
||||
new Function<Map.Entry<EntityType, Counter>, JSONPair>() {
|
||||
@Override
|
||||
public JSONPair apply(Map.Entry<EntityType, Counter> entry) {
|
||||
entityTypeSet.add(entry.getKey());
|
||||
return pair(
|
||||
String.valueOf(entry.getKey().getTypeId()),
|
||||
entry.getValue().count()
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
toObjectMapper(input.tileEntityCounts.entrySet(),
|
||||
new Function<Map.Entry<Material, Counter>, JSONPair>() {
|
||||
@Override
|
||||
public JSONPair apply(Map.Entry<Material, Counter> entry) {
|
||||
tileEntityTypeSet.add(entry.getKey());
|
||||
return pair(
|
||||
String.valueOf(entry.getKey().getId()),
|
||||
entry.getValue().count()
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
static class RegionData {
|
||||
final RegionId regionId;
|
||||
@SuppressWarnings("Guava")
|
||||
static Function<RegionId, RegionData> LOADER = new Function<RegionId, RegionData>() {
|
||||
@Override
|
||||
public RegionData apply(RegionId id) {
|
||||
return new RegionData(id);
|
||||
}
|
||||
};
|
||||
RegionData(RegionId id) {
|
||||
this.regionId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegionData that = (RegionData) o;
|
||||
|
||||
return regionId.equals(that.regionId);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return regionId.hashCode();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<EntityType, Counter> entityCounts = MRUMapCache.of(LoadingMap.of(
|
||||
new EnumMap<EntityType, Counter>(EntityType.class), Counter.LOADER
|
||||
));
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<Material, Counter> tileEntityCounts = MRUMapCache.of(LoadingMap.of(
|
||||
new EnumMap<Material, Counter>(Material.class), Counter.LOADER
|
||||
));
|
||||
|
||||
static class RegionId {
|
||||
final int x, z;
|
||||
final long regionId;
|
||||
RegionId(int x, int z) {
|
||||
this.x = x >> 5 << 5;
|
||||
this.z = z >> 5 << 5;
|
||||
this.regionId = ((long) (this.x) << 32) + (this.z >> 5 << 5) - Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RegionId regionId1 = (RegionId) o;
|
||||
|
||||
return regionId == regionId1.regionId;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (regionId ^ (regionId >>> 32));
|
||||
}
|
||||
}
|
||||
}
|
||||
static void resetTicks(boolean fullReset) {
|
||||
if (fullReset) {
|
||||
// Non full is simply for 1 minute reports
|
||||
timedTicks = 0;
|
||||
}
|
||||
lastMinuteTime = System.nanoTime();
|
||||
playerTicks = 0;
|
||||
tileEntityTicks = 0;
|
||||
entityTicks = 0;
|
||||
activatedEntityTicks = 0;
|
||||
}
|
||||
|
||||
Object export() {
|
||||
return createObject(
|
||||
pair("s", startTime),
|
||||
pair("e", endTime),
|
||||
pair("tk", totalTicks),
|
||||
pair("tm", totalTime),
|
||||
pair("w", worlds),
|
||||
pair("h", toArrayMapper(entries, new Function<TimingHistoryEntry, Object>() {
|
||||
@Override
|
||||
public Object apply(TimingHistoryEntry entry) {
|
||||
TimingData record = entry.data;
|
||||
if (!record.hasData()) {
|
||||
return null;
|
||||
}
|
||||
return entry.export();
|
||||
}
|
||||
})),
|
||||
pair("mp", toArrayMapper(minuteReports, new Function<MinuteReport, Object>() {
|
||||
@Override
|
||||
public Object apply(MinuteReport input) {
|
||||
return input.export();
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
static class MinuteReport {
|
||||
final long time = System.currentTimeMillis() / 1000;
|
||||
|
||||
final TicksRecord ticksRecord = new TicksRecord();
|
||||
final PingRecord pingRecord = new PingRecord();
|
||||
final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone();
|
||||
final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed;
|
||||
final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory;
|
||||
final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory;
|
||||
final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
|
||||
|
||||
List<Object> export() {
|
||||
return toArray(
|
||||
time,
|
||||
Math.round(tps * 100D) / 100D,
|
||||
Math.round(pingRecord.avg * 100D) / 100D,
|
||||
fst.export(),
|
||||
toArray(ticksRecord.timed,
|
||||
ticksRecord.player,
|
||||
ticksRecord.entity,
|
||||
ticksRecord.activatedEntity,
|
||||
ticksRecord.tileEntity
|
||||
),
|
||||
usedMemory,
|
||||
freeMemory,
|
||||
loadAvg
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TicksRecord {
|
||||
final long timed;
|
||||
final long player;
|
||||
final long entity;
|
||||
final long tileEntity;
|
||||
final long activatedEntity;
|
||||
|
||||
TicksRecord() {
|
||||
timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200);
|
||||
player = playerTicks;
|
||||
entity = entityTicks;
|
||||
tileEntity = tileEntityTicks;
|
||||
activatedEntity = activatedEntityTicks;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PingRecord {
|
||||
final double avg;
|
||||
|
||||
PingRecord() {
|
||||
final Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||
int totalPing = 0;
|
||||
for (Player player : onlinePlayers) {
|
||||
totalPing += player.spigot().getPing();
|
||||
}
|
||||
avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Counter {
|
||||
private int count = 0;
|
||||
@SuppressWarnings({"rawtypes", "SuppressionAnnotation", "Guava"})
|
||||
static Function LOADER = new LoadingMap.Feeder<Counter>() {
|
||||
@Override
|
||||
public Counter apply() {
|
||||
return new Counter();
|
||||
}
|
||||
};
|
||||
public int increment() {
|
||||
return ++count;
|
||||
}
|
||||
public int count() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
sources/src/main/java/co/aikar/timings/TimingsManager.java
Normal file
198
sources/src/main/java/co/aikar/timings/TimingsManager.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* This file is licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2014 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 com.google.common.collect.EvictingQueue;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.PluginClassLoader;
|
||||
import co.aikar.util.LoadingMap;
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Thread safe timing (safety)
|
||||
*/
|
||||
public final class TimingsManager {
|
||||
static final Map<TimingIdentifier, TimingHandler> TIMING_MAP =
|
||||
Collections.synchronizedMap(LoadingMap.newHashMap(
|
||||
TimingHandler::new,
|
||||
4096, .5F
|
||||
));
|
||||
public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
|
||||
public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
|
||||
public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
|
||||
public static List<String> hiddenConfigs = new ArrayList<String>();
|
||||
public static boolean privacy = false;
|
||||
|
||||
static final Collection<TimingHandler> HANDLERS = new ArrayDeque<TimingHandler>();
|
||||
static final ArrayDeque<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayDeque<TimingHistory.MinuteReport>();
|
||||
|
||||
static EvictingQueue<TimingHistory> HISTORY = EvictingQueue.create(12);
|
||||
static TimingHandler CURRENT;
|
||||
static long timingStart = 0;
|
||||
static long historyStart = 0;
|
||||
static boolean needsFullReset = false;
|
||||
static boolean needsRecheckEnabled = false;
|
||||
|
||||
private TimingsManager() {}
|
||||
|
||||
/**
|
||||
* Resets all timing data on the next tick
|
||||
*/
|
||||
static void reset() {
|
||||
needsFullReset = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticked every tick by CraftBukkit to count the number of times a timer
|
||||
* caused TPS loss.
|
||||
*/
|
||||
static void tick() {
|
||||
if (Timings.timingsEnabled) {
|
||||
boolean violated = FULL_SERVER_TICK.isViolated();
|
||||
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
for (TimingHandler handler : HANDLERS) {
|
||||
if (handler.isSpecial()) {
|
||||
// We manually call this
|
||||
continue;
|
||||
}
|
||||
handler.processTick(violated);
|
||||
}
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
|
||||
TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
|
||||
TimingHistory.timedTicks++;
|
||||
// Generate TPS/Ping/Tick reports every minute
|
||||
}
|
||||
}
|
||||
static void stopServer() {
|
||||
Timings.timingsEnabled = false;
|
||||
recheckEnabled();
|
||||
}
|
||||
static void recheckEnabled() {
|
||||
synchronized (TIMING_MAP) {
|
||||
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||
timings.checkEnabled();
|
||||
}
|
||||
}
|
||||
needsRecheckEnabled = false;
|
||||
}
|
||||
static void resetTimings() {
|
||||
if (needsFullReset) {
|
||||
// Full resets need to re-check every handlers enabled state
|
||||
// Timing map can be modified from async so we must sync on it.
|
||||
synchronized (TIMING_MAP) {
|
||||
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||
timings.reset(true);
|
||||
}
|
||||
}
|
||||
Bukkit.getLogger().log(Level.INFO, "Timings Reset");
|
||||
HISTORY.clear();
|
||||
needsFullReset = false;
|
||||
needsRecheckEnabled = false;
|
||||
timingStart = System.currentTimeMillis();
|
||||
} else {
|
||||
// Soft resets only need to act on timings that have done something
|
||||
// Handlers can only be modified on main thread.
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
for (TimingHandler timings : HANDLERS) {
|
||||
timings.reset(false);
|
||||
}
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
}
|
||||
|
||||
Akari.timingsLock.lock(); // Akarin
|
||||
HANDLERS.clear();
|
||||
Akari.timingsLock.unlock(); // Akarin
|
||||
MINUTE_REPORTS.clear();
|
||||
|
||||
TimingHistory.resetTicks(true);
|
||||
historyStart = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
static TimingHandler getHandler(String group, String name, Timing parent) {
|
||||
return TIMING_MAP.get(new TimingIdentifier(group, name, parent));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Due to access restrictions, we need a helper method to get a Command TimingHandler with String group</p>
|
||||
*
|
||||
* Plugins should never call this
|
||||
*
|
||||
* @param pluginName Plugin this command is associated with
|
||||
* @param command Command to get timings for
|
||||
* @return TimingHandler
|
||||
*/
|
||||
public static Timing getCommandTiming(String pluginName, Command command) {
|
||||
Plugin plugin = null;
|
||||
final Server server = Bukkit.getServer();
|
||||
if (!( server == null || pluginName == null ||
|
||||
"minecraft".equals(pluginName) || "bukkit".equals(pluginName) ||
|
||||
"spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName)
|
||||
)) {
|
||||
plugin = server.getPluginManager().getPlugin(pluginName);
|
||||
}
|
||||
if (plugin == null) {
|
||||
// Plugin is passing custom fallback prefix, try to look up by class loader
|
||||
plugin = getPluginByClassloader(command.getClass());
|
||||
}
|
||||
if (plugin == null) {
|
||||
return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
|
||||
}
|
||||
|
||||
return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
|
||||
* Plugin that created this class.
|
||||
*
|
||||
* @param clazz Class to check
|
||||
* @return Plugin if created by a plugin
|
||||
*/
|
||||
public static Plugin getPluginByClassloader(Class<?> clazz) {
|
||||
if (clazz == null) {
|
||||
return null;
|
||||
}
|
||||
final ClassLoader classLoader = clazz.getClassLoader();
|
||||
if (classLoader instanceof PluginClassLoader) {
|
||||
PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
|
||||
return pluginClassLoader.getPlugin();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +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 com.google.common.collect.Queues;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import co.aikar.timings.Timings;
|
||||
|
||||
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<Void> STAGE_TICK = new ExecutorCompletionService<Void>(Executors.newFixedThreadPool(1, Akari.STAGE_FACTORY));
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
sources/src/main/java/io/akarin/api/internal/Akari.java
Normal file
149
sources/src/main/java/io/akarin/api/internal/Akari.java
Normal file
@@ -0,0 +1,149 @@
|
||||
package io.akarin.api.internal;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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.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;
|
||||
|
||||
@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 SuspendableExecutorCompletionService<?> 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() + ")";
|
||||
}
|
||||
|
||||
public static final ReentrantSpinningLock eventLock = new ReentrantSpinningLock();
|
||||
public static final ReentrantSpinningLock timingsLock = new ReentrantSpinningLock();
|
||||
|
||||
/*
|
||||
* Timings
|
||||
*/
|
||||
public final static Timing eventSuspendTiming = getTiming("Akarin - Event Suspend");
|
||||
|
||||
public final static Timing eventResumeTiming = getTiming("Akarin - Event Resume");
|
||||
|
||||
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,9 @@
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public interface IMixinWorldServer {
|
||||
public Object tickLock();
|
||||
public Random rand();
|
||||
public Object trackLock();
|
||||
}
|
||||
@@ -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,98 @@
|
||||
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.
|
||||
*/
|
||||
private final AtomicBoolean writeLocked = new AtomicBoolean(false);
|
||||
|
||||
// --------- Thread local restricted fields ---------
|
||||
private long heldThreadId = 0;
|
||||
private int reentrantLocks = 0;
|
||||
|
||||
/**
|
||||
* Lock as a typical 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 read lock
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpinningReadLock {
|
||||
public void lock() {
|
||||
lockWeak();
|
||||
}
|
||||
public void unlock() {
|
||||
unlockWeak();
|
||||
}
|
||||
}
|
||||
|
||||
public SpinningWriteLock writeLock() {
|
||||
return wrappedWriteLock;
|
||||
}
|
||||
|
||||
public SpinningReadLock readLocked() {
|
||||
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
@@ -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,8 +16,8 @@ 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 {
|
||||
|
||||
private static File CONFIG_FILE;
|
||||
@@ -23,7 +25,7 @@ public class AkarinGlobalConfig {
|
||||
+ "Some options may impact gameplay, so use with caution,\n"
|
||||
+ "and make sure you know what each option does before configuring.\n"
|
||||
+ "\n"
|
||||
+ "Akarin forums: https://akarin.io/ \n";
|
||||
+ "Akarin website: https://akarin.io/ \n";
|
||||
/*========================================================================*/
|
||||
public static YamlConfiguration config;
|
||||
static int version;
|
||||
@@ -153,43 +155,83 @@ public class AkarinGlobalConfig {
|
||||
legacyVersioningCompat = getBoolean("alternative.legacy-versioning-compat", false);
|
||||
}
|
||||
|
||||
public static int registryTerminationSeconds;
|
||||
private static void registryTerminationSeconds() {
|
||||
registryTerminationSeconds = getSeconds(getString("bootstrap.parallel-registry-termination", "9s"));
|
||||
}
|
||||
|
||||
public static int playersPerIOThread;
|
||||
private static void playersPerIOThread() {
|
||||
playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50);
|
||||
}
|
||||
|
||||
public static boolean silentAsyncTimings;
|
||||
private static void silentAsyncTimings() {
|
||||
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"));
|
||||
keepAliveTimeout = getSeconds(getString("core.keep-alive-response-timeout", "30s")) * 1000;
|
||||
}
|
||||
|
||||
public static boolean throwOnAsyncCaught;
|
||||
private static void throwOnAsyncCaught() {
|
||||
throwOnAsyncCaught = getBoolean("core.thread-safe.async-catcher.throw-on-caught", true);
|
||||
}
|
||||
|
||||
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 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,79 @@ 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()) {
|
||||
// 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.getWorlds()) {
|
||||
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 {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Akari.logger.warn("Slack scheduler thread was interrupted unexpectly!");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
@@ -41,6 +41,7 @@ public class Bootstrap {
|
||||
Akari.logger.warn("Visit our website for latest information https://akarin.io/");
|
||||
}
|
||||
|
||||
/*
|
||||
@Redirect(method = "main", at = @At(
|
||||
value = "INVOKE_STRING",
|
||||
target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V",
|
||||
@@ -49,4 +50,5 @@ public class Bootstrap {
|
||||
private static void notifyLoading(PrintStream stream, String text) {
|
||||
Akari.logger.info("Loading libraries as parallel capable..");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package io.akarin.server.mixin.bootstrap;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import io.akarin.api.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.BiomeBase;
|
||||
import net.minecraft.server.Block;
|
||||
import net.minecraft.server.BlockFire;
|
||||
import net.minecraft.server.DispenserRegistry;
|
||||
import net.minecraft.server.Enchantment;
|
||||
import net.minecraft.server.EntityTypes;
|
||||
import net.minecraft.server.Item;
|
||||
import net.minecraft.server.MobEffectList;
|
||||
import net.minecraft.server.PotionBrewer;
|
||||
import net.minecraft.server.PotionRegistry;
|
||||
import net.minecraft.server.SoundEffect;
|
||||
|
||||
@Mixin(value = DispenserRegistry.class, remap = false)
|
||||
public class ParallelRegistry {
|
||||
/**
|
||||
* Registry order: SoundEffect -> Block
|
||||
*/
|
||||
private static final ExecutorService STAGE_BLOCK = Executors.newSingleThreadExecutor(Akari.STAGE_FACTORY);
|
||||
/**
|
||||
* Registry order: Item -> PotionBrewer & orderless: BlockFire, BiomeBase (After STAGE_BLOCK)
|
||||
*/
|
||||
private static final ExecutorService STAGE_BLOCK_BASE = Executors.newFixedThreadPool(3, Akari.STAGE_FACTORY);
|
||||
|
||||
/**
|
||||
* Registry order: MobEffectList -> PotionRegistry & orderless: Enchantment, EntityTypes
|
||||
*/
|
||||
private static final ExecutorService STAGE_STANDALONE = Executors.newFixedThreadPool(3, Akari.STAGE_FACTORY);
|
||||
|
||||
private static final int TERMINATION_IN_SEC = AkarinGlobalConfig.registryTerminationSeconds;
|
||||
|
||||
// We should keep the original order in codes thought orderless in runtime
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/SoundEffect.b()V"
|
||||
))
|
||||
private static void soundEffect() {
|
||||
STAGE_BLOCK.execute(() -> {
|
||||
SoundEffect.b();
|
||||
Block.w();
|
||||
|
||||
STAGE_BLOCK_BASE.execute(() -> BlockFire.e()); // This single task only cost ~4ms, however, firing a task only takes ~1ms
|
||||
STAGE_BLOCK_BASE.execute(() -> {
|
||||
Item.t();
|
||||
PotionBrewer.a();
|
||||
});
|
||||
STAGE_BLOCK_BASE.execute(() -> BiomeBase.q());
|
||||
});
|
||||
}
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/Block.w()V"
|
||||
))
|
||||
private static void block() {} // STAGE_BLOCK
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/BlockFire.e()V"
|
||||
))
|
||||
private static void blockFire() {} // STAGE_BLOCK_BASE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/MobEffectList.k()V"
|
||||
))
|
||||
private static void mobEffectList() {} // STAGE_STANDALONE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/Enchantment.g()V"
|
||||
))
|
||||
private static void enchantment() {
|
||||
STAGE_STANDALONE.execute(() -> Enchantment.g());
|
||||
STAGE_STANDALONE.execute(() -> EntityTypes.c());
|
||||
STAGE_STANDALONE.execute(() -> {
|
||||
MobEffectList.k();
|
||||
PotionRegistry.b();
|
||||
});
|
||||
}
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/Item.t()V"
|
||||
))
|
||||
private static void item() {} // STAGE_BLOCK_BASE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/PotionRegistry.b()V"
|
||||
))
|
||||
private static void potionRegistry() {} // STAGE_STANDALONE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/PotionBrewer.a()V"
|
||||
))
|
||||
private static void potionBrewer() {} // STAGE_BLOCK_BASE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/EntityTypes.c()V"
|
||||
))
|
||||
private static void entityTypes() {} // STAGE_STANDALONE
|
||||
|
||||
@Redirect(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/BiomeBase.q()V"
|
||||
))
|
||||
private static void biomeBase() {} // STAGE_BLOCK_BASE
|
||||
|
||||
@Inject(method = "c()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/DispenserRegistry.b()V",
|
||||
shift = At.Shift.BEFORE
|
||||
))
|
||||
private static void await(CallbackInfo info) throws InterruptedException {
|
||||
// Shutdown BLOCK and STANDALONE stage
|
||||
STAGE_STANDALONE.shutdown();
|
||||
STAGE_BLOCK.shutdown();
|
||||
STAGE_BLOCK.awaitTermination(TERMINATION_IN_SEC, TimeUnit.SECONDS);
|
||||
|
||||
STAGE_BLOCK_BASE.shutdown(); // This must after STAGE_BLOCK terminated
|
||||
STAGE_BLOCK_BASE.awaitTermination(TERMINATION_IN_SEC, TimeUnit.SECONDS);
|
||||
|
||||
STAGE_STANDALONE.awaitTermination(TERMINATION_IN_SEC, TimeUnit.SECONDS); // Behind the shutdown of BLOCK_BASE should faster
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spigotmc.AsyncCatcher;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(value = AsyncCatcher.class, remap = false)
|
||||
public class DesyncCatcher {
|
||||
@Shadow public static boolean enabled;
|
||||
|
||||
@Overwrite
|
||||
public static void catchOp(String reason) {
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spigotmc.AsyncCatcher;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
@Mixin(value = AsyncCatcher.class, remap = false)
|
||||
public abstract class MixinAsyncCatcher {
|
||||
@Shadow public static boolean enabled;
|
||||
|
||||
@Overwrite
|
||||
public static void catchOp(String reason) {
|
||||
if (enabled && !Akari.isPrimaryThread()) {
|
||||
if (AkarinGlobalConfig.throwOnAsyncCaught) {
|
||||
throw new IllegalStateException("Asynchronous " + reason + "!");
|
||||
} else {
|
||||
Akari.logger.warn("Asynchronous " + reason + "!");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -7,19 +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 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 String getVersion() {
|
||||
if (needApplyServerVersion) {
|
||||
serverVersion = AkarinGlobalConfig.serverBrandName.equals(Akari.EMPTY_STRING) ? serverVersion : serverVersion.replace("Akarin", AkarinGlobalConfig.serverBrandName);
|
||||
needApplyServerVersion = false;
|
||||
}
|
||||
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 = "f") 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,49 @@
|
||||
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 void ensureMain(String reason, Runnable run) {
|
||||
if (AsyncCatcher.enabled && !Akari.isPrimaryThread()) { // Akarin
|
||||
if (reason != null) {
|
||||
new IllegalStateException("Asynchronous " + reason + "!").printStackTrace();
|
||||
}
|
||||
MinecraftServer.getServer().processQueue.add(run);
|
||||
return;
|
||||
}
|
||||
run.run();
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public static <T> T ensureMain(String reason, Supplier<T> run) {
|
||||
if (AsyncCatcher.enabled && !Akari.isPrimaryThread()) { // Akarin
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
@@ -16,15 +21,16 @@ 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.mixin.IMixinWorldServer;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import io.akarin.server.core.AkarinSlackScheduler;
|
||||
import net.minecraft.server.CrashReport;
|
||||
import net.minecraft.server.CustomFunctionData;
|
||||
import net.minecraft.server.DimensionManager;
|
||||
import net.minecraft.server.ITickable;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.MojangStatisticsGenerator;
|
||||
import net.minecraft.server.PlayerList;
|
||||
import net.minecraft.server.ReportedException;
|
||||
import net.minecraft.server.ServerConnection;
|
||||
import net.minecraft.server.SystemUtils;
|
||||
@@ -32,50 +38,64 @@ import net.minecraft.server.TileEntityHopper;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = MinecraftServer.class, remap = false)
|
||||
public class MixinMinecraftServer {
|
||||
public abstract class MixinMinecraftServer {
|
||||
@Shadow @Final public Thread primaryThread;
|
||||
private int cachedWorldSize;
|
||||
|
||||
@Overwrite
|
||||
public String getServerModName() {
|
||||
return "Akarin";
|
||||
}
|
||||
|
||||
@Inject(method = "run()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/SystemUtils.b()J",
|
||||
shift = At.Shift.BEFORE
|
||||
))
|
||||
private void prerun(CallbackInfo info) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
|
||||
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
||||
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
||||
Akari.resizeTickExecutors((cachedWorldSize = worldServer.size()));
|
||||
|
||||
Field skipHopperEvents = TileEntityHopper.class.getDeclaredField("skipHopperEvents"); // No idea why static but check each world
|
||||
skipHopperEvents.setAccessible(true);
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
skipHopperEvents.set(null, world.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0);
|
||||
}
|
||||
AkarinSlackScheduler.get().boot();
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public boolean isMainThread() {
|
||||
return Akari.isPrimaryThread();
|
||||
}
|
||||
|
||||
/*
|
||||
* Forcely disable snooper
|
||||
*/
|
||||
@Overwrite
|
||||
public void a(MojangStatisticsGenerator generator) {}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
AkarinSlackScheduler.boot();
|
||||
}
|
||||
|
||||
/*
|
||||
* Parallel world ticking
|
||||
*/
|
||||
@Shadow public CraftServer server;
|
||||
@Shadow @Mutable protected Queue<FutureTask<?>> j;
|
||||
@Shadow @Mutable protected Queue<FutureTask<?>> f;
|
||||
@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 @Final public Map<DimensionManager, WorldServer> worldServer;
|
||||
@Shadow(aliases = "k") @Final private List<ITickable> tickables;
|
||||
|
||||
@Shadow public PlayerList getPlayerList() { return null; }
|
||||
@Shadow public ServerConnection an() { return null; }
|
||||
@Shadow public CustomFunctionData aL() { return null; }
|
||||
@Shadow public abstract CustomFunctionData getFunctionData();
|
||||
@Shadow public abstract ServerConnection getServerConnection();
|
||||
|
||||
private void tickEntities(WorldServer world) {
|
||||
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 {
|
||||
@@ -86,11 +106,14 @@ public class MixinMinecraftServer {
|
||||
world.a(crashreport);
|
||||
throw new ReportedException(crashreport);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void tickWorld(WorldServer world) {
|
||||
private void tickWorld(WorldServer world, BooleanSupplier supplier) {
|
||||
try {
|
||||
world.doTick();
|
||||
world.timings.doTick.startTiming();
|
||||
world.doTick(supplier);
|
||||
world.timings.doTick.stopTiming();
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport;
|
||||
try {
|
||||
@@ -104,24 +127,24 @@ public class MixinMinecraftServer {
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void D() throws InterruptedException {
|
||||
public void b(BooleanSupplier supplier) throws InterruptedException, ExecutionException {
|
||||
Runnable runnable;
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.stopTiming();
|
||||
|
||||
MinecraftTimings.bukkitSchedulerTimer.startTiming();
|
||||
this.server.getScheduler().mainThreadHeartbeat(this.ticks);
|
||||
MinecraftTimings.bukkitSchedulerTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.minecraftSchedulerTimer.startTiming();
|
||||
FutureTask<?> task;
|
||||
int count = j.size();
|
||||
while (count-- > 0 && (task = j.poll()) != null) {
|
||||
int count = f.size();
|
||||
while (count-- > 0 && (task = f.poll()) != null) {
|
||||
SystemUtils.a(task, MinecraftServer.LOGGER);
|
||||
}
|
||||
MinecraftTimings.minecraftSchedulerTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.commandFunctionsTimer.startTiming();
|
||||
getFunctionData().Y_();
|
||||
MinecraftTimings.commandFunctionsTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.processQueueTimer.startTiming();
|
||||
while ((runnable = processQueue.poll()) != null) runnable.run();
|
||||
MinecraftTimings.processQueueTimer.stopTiming();
|
||||
@@ -130,75 +153,105 @@ public 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.tickLock) {
|
||||
if (cachedWorldSize != worldServer.size()) Akari.resizeTickExecutors((cachedWorldSize = worldServer.size()));
|
||||
switch (AkarinGlobalConfig.parallelMode) {
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
// Never tick one world concurrently!
|
||||
WorldServer interlacedWorld = null;
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
if (interlacedWorld == null) {
|
||||
interlacedWorld = world;
|
||||
} else {
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinWorldServer) world).tickLock()) {
|
||||
tickEntities(world);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode != 1 /* >= 2 */) {
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinWorldServer) world).tickLock()) {
|
||||
tickWorld(world, supplier);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
WorldServer fInterlacedWorld = interlacedWorld;
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinWorldServer) fInterlacedWorld).tickLock()) {
|
||||
tickEntities(fInterlacedWorld);
|
||||
}
|
||||
}, null);
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode == 1)
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
synchronized (((IMixinWorldServer) world).tickLock()) {
|
||||
tickWorld(world, supplier);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize + 1 : cachedWorldSize * 2); i --> 0 ;) {
|
||||
Akari.STAGE_TICK.take();
|
||||
}
|
||||
|
||||
break;
|
||||
case 0:
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
WorldServer interlacedWorld_ = null;
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
if (interlacedWorld_ == null) {
|
||||
interlacedWorld_ = world;
|
||||
continue;
|
||||
}
|
||||
synchronized (((IMixinWorldServer) world).tickLock()) {
|
||||
tickEntities(world);
|
||||
}
|
||||
}
|
||||
synchronized (((IMixinWorldServer) interlacedWorld_).tickLock()) {
|
||||
tickEntities(interlacedWorld_);
|
||||
}
|
||||
}, null);
|
||||
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
synchronized (((IMixinWorldServer) world).tickLock()) {
|
||||
tickWorld(world, supplier);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
Akari.STAGE_TICK.take();
|
||||
Akari.STAGE_TICK.take();
|
||||
break;
|
||||
case -1:
|
||||
for (WorldServer world : worldServer.values()) {
|
||||
tickWorld(world, supplier);
|
||||
tickEntities(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (world.tickLock) {
|
||||
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();
|
||||
getServerConnection().c();
|
||||
MinecraftTimings.connectionTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.playerListTimer.startTiming();
|
||||
this.v.tick();
|
||||
MinecraftTimings.playerListTimer.stopTiming();
|
||||
|
||||
MinecraftTimings.commandFunctionsTimer.startTiming();
|
||||
this.aL().e();
|
||||
MinecraftTimings.commandFunctionsTimer.stopTiming();
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.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).Y_();
|
||||
}
|
||||
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(Packet<T> packet, 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,55 +1,32 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
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;
|
||||
import co.aikar.timings.Timing;
|
||||
|
||||
@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false)
|
||||
public class MixinTimingHandler {
|
||||
public abstract class MixinTimingHandler {
|
||||
@Shadow @Final String name;
|
||||
@Shadow private boolean enabled;
|
||||
@Shadow private volatile long start;
|
||||
@Shadow private volatile int timingDepth;
|
||||
@Shadow private AtomicLong start;
|
||||
@Shadow private AtomicInteger timingDepth;
|
||||
|
||||
@Overwrite
|
||||
public void stopTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
stopTiming(true); // Avoid twice thread check
|
||||
}
|
||||
}
|
||||
@Shadow abstract void addDiff(long diff);
|
||||
@Shadow public abstract Timing startTiming();
|
||||
|
||||
@Overwrite
|
||||
public void stopTiming() {
|
||||
// Akarin start - avoid twice thread check
|
||||
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 && !Bukkit.isPrimaryThread()) {
|
||||
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;
|
||||
}
|
||||
public void stopTiming(boolean alreadySync) {
|
||||
if (!enabled || start.get() == 0 || timingDepth.decrementAndGet() != 0) return;
|
||||
long prev = start.getAndSet(0); // Akarin
|
||||
addDiff(System.nanoTime() - prev); // Akarin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ 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.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 {
|
||||
@Shadow private static int getFromRepo(String repo, String branch, String hash) { return 0; }
|
||||
|
||||
/**
|
||||
* Match current version with repository and calculate the distance
|
||||
@@ -26,7 +26,7 @@ public class MixinVersionCommand {
|
||||
@Overwrite
|
||||
private static int getDistance(String repo, String verInfo) {
|
||||
verInfo = verInfo.replace("\"", "");
|
||||
return getFromRepo("Akarin-project/Akarin", verInfo);
|
||||
return getFromRepo("Akarin-project/Akarin", "ver/1.13", verInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ public class MixinVersionCommand {
|
||||
@Overwrite
|
||||
private static int getFromJenkins(int currentVer) {
|
||||
String[] parts = Bukkit.getVersion().substring("git-Akarin-".length()).split("[-\\s]");
|
||||
return getFromRepo("Akarin-project/Akarin", parts[0]);
|
||||
return getFromRepo("Akarin-project/Akarin","ver/1.13", parts[0]);
|
||||
}
|
||||
|
||||
@Shadow private boolean hasVersion;
|
||||
@@ -59,6 +59,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 +73,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 {
|
||||
@@ -79,7 +82,7 @@ public class MixinVersionCommand {
|
||||
}
|
||||
}
|
||||
if (!hasVersion) {
|
||||
obtainVersion(sender);
|
||||
obtainVersionAsync(sender);
|
||||
if (AkarinGlobalConfig.legacyVersioningCompat) currentSender = sender;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +90,7 @@ public class MixinVersionCommand {
|
||||
@Overwrite
|
||||
private void obtainVersion() {
|
||||
if (AkarinGlobalConfig.legacyVersioningCompat) {
|
||||
obtainVersion(currentSender);
|
||||
obtainVersionAsync(currentSender);
|
||||
currentSender = null; // try release
|
||||
} else {
|
||||
Akari.logger.warn("A legacy version lookup was caught, legacy-versioning-compat enabled forcely!");
|
||||
@@ -96,15 +99,13 @@ public class MixinVersionCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private void obtainVersion(CommandSender sender) {
|
||||
private void obtainVersionAsync(CommandSender sender) {
|
||||
// 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 +141,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,28 @@
|
||||
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 io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
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) {
|
||||
synchronized (((IMixinWorldServer) this.world).trackLock()) { // Akarin
|
||||
this.world.getTracker().track(entity);
|
||||
}
|
||||
|
||||
if (entity instanceof EntityPlayer) {
|
||||
this.world.worldProvider.a((EntityPlayer) entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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 tickLock() {
|
||||
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;
|
||||
}
|
||||
|
||||
public final Object trackLock = new Object();
|
||||
|
||||
@Override
|
||||
public Object trackLock() {
|
||||
return trackLock;
|
||||
}
|
||||
}
|
||||
@@ -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,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,41 +18,50 @@ 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.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
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");
|
||||
|
||||
/**
|
||||
* Contains all endpoints added to this NetworkSystem
|
||||
*/
|
||||
@Shadow @Mutable @Final private List<ChannelFuture> g;
|
||||
@Shadow(aliases = "f") @Mutable @Final private List<ChannelFuture> endPoints;
|
||||
/**
|
||||
* A list containing all NetworkManager instances of all endpoints
|
||||
*/
|
||||
@Shadow @Mutable @Final private List<NetworkManager> h;
|
||||
@Shadow(aliases = "g") @Mutable @Final private List<NetworkManager> networkManagers;
|
||||
|
||||
@Overwrite
|
||||
private void addPending() {} // just keep compatibility
|
||||
|
||||
@Shadow @Final private MinecraftServer f;
|
||||
@Shadow(aliases = "e") @Final private MinecraftServer server;
|
||||
|
||||
/**
|
||||
* Adds channels (endpoint) that listens on publicly accessible network ports
|
||||
@@ -65,18 +75,37 @@ public class NonblockingServerConnection {
|
||||
Class<? extends ServerChannel> channelClass;
|
||||
EventLoopGroup loopGroup;
|
||||
|
||||
if (Epoll.isAvailable() && this.f.af()) { // PAIL: MinecraftServer::useNativeTransport
|
||||
if (Epoll.isAvailable() && this.server.V()) { // OBFHELPER: MinecraftServer::useNativeTransport
|
||||
channelClass = EpollServerSocketChannel.class;
|
||||
loopGroup = ServerConnection.b.c();
|
||||
loopGroup = ServerConnection.b.a();
|
||||
logger.info("Using epoll channel type");
|
||||
} else {
|
||||
channelClass = NioServerSocketChannel.class;
|
||||
loopGroup = ServerConnection.a.c();
|
||||
loopGroup = ServerConnection.a.a();
|
||||
logger.info("Using nio channel type");
|
||||
}
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap().channel(channelClass).childHandler(ChannelAdapter.create(h)).group(loopGroup);
|
||||
synchronized (g) {
|
||||
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(":");
|
||||
try {
|
||||
@@ -87,19 +116,19 @@ public class NonblockingServerConnection {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
data.forEach(address -> g.add(bootstrap.localAddress(address.host(), address.port()).bind().syncUninterruptibly())); // supports multi-port bind
|
||||
data.forEach(address -> endPoints.add(bootstrap.localAddress(address.host(), address.port()).bind().syncUninterruptibly())); // supports multi-port bind
|
||||
}
|
||||
}
|
||||
|
||||
@Shadow public volatile boolean d; // PAIL: neverTerminate
|
||||
@Shadow public volatile boolean c; // OBFHELPER: neverTerminate
|
||||
/**
|
||||
* Shuts down all open endpoints
|
||||
*/
|
||||
public void b() {
|
||||
this.d = false;
|
||||
this.c = false;
|
||||
try {
|
||||
synchronized (g) { // safe fixes
|
||||
for (ChannelFuture channel : g) channel.channel().close().sync();
|
||||
synchronized (endPoints) { // safe fixes
|
||||
for (ChannelFuture channel : endPoints) channel.channel().close().sync();
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
logger.error("Interrupted whilst closing channel");
|
||||
@@ -108,17 +137,12 @@ 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>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws Exception {
|
||||
manager.close(kick);
|
||||
}
|
||||
}, new GenericFutureListener[0]);
|
||||
manager.sendPacket(new PacketPlayOutKickDisconnect(message), (future) -> manager.close(message));
|
||||
manager.stopReading();
|
||||
}
|
||||
}
|
||||
@@ -128,16 +152,16 @@ public class NonblockingServerConnection {
|
||||
*/
|
||||
@Overwrite
|
||||
public void c() throws InterruptedException {
|
||||
synchronized (h) {
|
||||
synchronized (networkManagers) {
|
||||
// Spigot - This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
|
||||
if (SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % SpigotConfig.playerShuffle == 0) {
|
||||
Collections.shuffle(h);
|
||||
Collections.shuffle(networkManagers);
|
||||
}
|
||||
|
||||
Iterator<NetworkManager> it = h.iterator();
|
||||
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;
|
||||
@@ -18,26 +18,26 @@ import net.minecraft.server.Packet;
|
||||
import net.minecraft.server.PacketPlayOutMapChunk;
|
||||
|
||||
@Mixin(value = NetworkManager.class, remap = false)
|
||||
public class OptimisticNetworkManager {
|
||||
public abstract class OptimisticNetworkManager {
|
||||
@Shadow public Channel channel;
|
||||
@Shadow @Final private Queue<NetworkManager.QueuedPacket> i;
|
||||
@Shadow @Final private ReentrantReadWriteLock j;
|
||||
@Shadow(aliases = "i") @Final private Queue<NetworkManager.QueuedPacket> packets;
|
||||
@Shadow(aliases = "j") @Final private ReentrantReadWriteUpdateLock queueLock;
|
||||
|
||||
@Shadow private Queue<NetworkManager.QueuedPacket> getPacketQueue() { return null; }
|
||||
@Shadow private void dispatchPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>>[] genericFutureListeners) {}
|
||||
@Shadow public abstract Queue<NetworkManager.QueuedPacket> getPacketQueue();
|
||||
@Shadow public abstract void dispatchPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericFutureListener);
|
||||
|
||||
private static final QueuedPacket SIGNAL_PACKET = new QueuedPacket(null, null);
|
||||
|
||||
@Overwrite
|
||||
private boolean m() {
|
||||
@Overwrite // OBFHELPER: trySendQueue
|
||||
private boolean o() {
|
||||
if (this.channel != null && this.channel.isOpen()) {
|
||||
if (this.i.isEmpty()) { // return if the packet queue is empty so that the write lock by Anti-Xray doesn't affect the vanilla performance at all
|
||||
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.j.readLock().lock();
|
||||
this.queueLock.updateLock().lock();
|
||||
try {
|
||||
while (!this.i.isEmpty()) {
|
||||
while (!this.packets.isEmpty()) {
|
||||
NetworkManager.QueuedPacket packet = ((CheckedConcurrentLinkedQueue<QueuedPacket>) getPacketQueue()).poll(item -> {
|
||||
return item.getPacket() instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) item.getPacket()).isReady();
|
||||
}, SIGNAL_PACKET);
|
||||
@@ -46,12 +46,12 @@ public class OptimisticNetworkManager {
|
||||
if (packet == SIGNAL_PACKET) {
|
||||
return false; // Return false if the peeked packet is a chunk packet which is not ready
|
||||
} else {
|
||||
dispatchPacket(packet.getPacket(), packet.getGenericFutureListeners()); // dispatch the packet
|
||||
dispatchPacket(packet.getPacket(), packet.getGenericFutureListener()); // dispatch the packet
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.j.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 ax() {
|
||||
/*
|
||||
* 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().f(0.10000000149011612D, 0.4000000059604645D, 0.10000000149011612D), Material.LAVA);
|
||||
}
|
||||
return this.isInLava;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -33,36 +34,36 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import net.minecraft.server.DataWatcherObject;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.EntityHorseAbstract;
|
||||
import net.minecraft.server.EntityTypes;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = EntityHorseAbstract.class, remap = false)
|
||||
public abstract class MixinEntityHorseAbstract extends Entity {
|
||||
@Shadow @Final private static DataWatcherObject<Optional<UUID>> bJ;
|
||||
@Shadow(aliases = "bO") @Final private static DataWatcherObject<Optional<UUID>> OWNER_UNIQUE_ID;
|
||||
|
||||
@Nullable private Optional<UUID> cachedOwnerId;
|
||||
|
||||
@Nullable
|
||||
@Overwrite
|
||||
@Nullable public UUID getOwnerUUID() {
|
||||
if (cachedOwnerId == null) cachedOwnerId = datawatcher.get(bJ);
|
||||
return cachedOwnerId.orNull();
|
||||
public UUID getOwnerUUID() {
|
||||
if (cachedOwnerId == null) cachedOwnerId = datawatcher.get(OWNER_UNIQUE_ID);
|
||||
return cachedOwnerId.orElse(null);
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void setOwnerUUID(@Nullable UUID uuid) {
|
||||
cachedOwnerId = Optional.fromNullable(uuid);
|
||||
datawatcher.set(bJ, cachedOwnerId);
|
||||
cachedOwnerId = Optional.ofNullable(uuid);
|
||||
datawatcher.set(OWNER_UNIQUE_ID, cachedOwnerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends from superclass
|
||||
* @param world
|
||||
*/
|
||||
public MixinEntityHorseAbstract(World world) {
|
||||
super(world);
|
||||
public MixinEntityHorseAbstract(EntityTypes<?> entitytypes, World world) {
|
||||
super(entitytypes, world);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -33,36 +34,36 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import net.minecraft.server.DataWatcherObject;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.EntityTameableAnimal;
|
||||
import net.minecraft.server.EntityTypes;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = EntityTameableAnimal.class, remap = false)
|
||||
public abstract class MixinEntityTameableAnimal extends Entity {
|
||||
@Shadow @Final protected static DataWatcherObject<Optional<UUID>> by;
|
||||
@Shadow @Final protected static DataWatcherObject<Optional<UUID>> bD;
|
||||
|
||||
@Nullable private Optional<UUID> cachedOwnerId;
|
||||
|
||||
@Nullable
|
||||
@Overwrite
|
||||
@Nullable public UUID getOwnerUUID() {
|
||||
if (cachedOwnerId == null) cachedOwnerId = datawatcher.get(by);
|
||||
return cachedOwnerId.orNull();
|
||||
public UUID getOwnerUUID() {
|
||||
if (cachedOwnerId == null) cachedOwnerId = datawatcher.get(bD);
|
||||
return cachedOwnerId.orElse(null);
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void setOwnerUUID(@Nullable UUID uuid) {
|
||||
cachedOwnerId = Optional.fromNullable(uuid);
|
||||
datawatcher.set(by, cachedOwnerId);
|
||||
cachedOwnerId = Optional.ofNullable(uuid);
|
||||
datawatcher.set(bD, cachedOwnerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends from superclass
|
||||
* @param world
|
||||
*/
|
||||
public MixinEntityTameableAnimal(World world) {
|
||||
super(world);
|
||||
public MixinEntityTameableAnimal(EntityTypes<?> entitytypes, World world) {
|
||||
super(entitytypes, world);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
public void Y_() {} // No tickable
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package io.akarin.server.mixin.optimization;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.WorldGenBigTree;
|
||||
|
||||
/**
|
||||
* Fixes MC-128547(https://bugs.mojang.com/browse/MC-128547)
|
||||
*/
|
||||
@Mixin(value = WorldGenBigTree.class, remap = false)
|
||||
public class WeakBigTree {
|
||||
@Shadow private World l;
|
||||
|
||||
@Inject(method = "generate", at = @At("RETURN"))
|
||||
private void clearWorldRef(World world, Random random, BlockPosition pos, CallbackInfoReturnable<?> info) {
|
||||
l = null; // Akarin - remove references to world objects to avoid memory leaks
|
||||
}
|
||||
}
|
||||
@@ -1,95 +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.optimization;
|
||||
|
||||
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.DamageSource;
|
||||
import net.minecraft.server.EnchantmentManager;
|
||||
import net.minecraft.server.Entity;
|
||||
import net.minecraft.server.EntityHuman;
|
||||
import net.minecraft.server.EntityLiving;
|
||||
import net.minecraft.server.ItemStack;
|
||||
|
||||
/**
|
||||
* Fixes MC-128547(https://bugs.mojang.com/browse/MC-128547)
|
||||
*/
|
||||
@Mixin(value = EnchantmentManager.class, remap = false)
|
||||
public class WeakEnchantmentManager {
|
||||
@Shadow @Final private static EnchantmentManager.EnchantmentModifierProtection a;
|
||||
@Shadow @Final private static EnchantmentManager.EnchantmentModifierThorns c;
|
||||
@Shadow @Final private static EnchantmentManager.EnchantmentModifierArthropods d;
|
||||
|
||||
@Shadow private static void a(EnchantmentManager.EnchantmentModifier modifier, Iterable<ItemStack> iterable) {}
|
||||
@Shadow private static void a(EnchantmentManager.EnchantmentModifier modifier, ItemStack itemstack) {}
|
||||
|
||||
@Overwrite
|
||||
public static int a(Iterable<ItemStack> iterable, DamageSource damageSource) {
|
||||
EnchantmentManager.a.a = 0; // PAIL: damageModifier
|
||||
EnchantmentManager.a.b = damageSource;
|
||||
a(EnchantmentManager.a, iterable);
|
||||
a.b = null; // Akarin - Remove reference to Damagesource
|
||||
return EnchantmentManager.a.a;
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public static void a(EntityLiving user, Entity attacker) { // PAIL: applyThornEnchantments
|
||||
EnchantmentManager.c.b = attacker;
|
||||
EnchantmentManager.c.a = user;
|
||||
if (user != null) {
|
||||
a(EnchantmentManager.c, user.aQ()); // PAIL: applyEnchantmentModifierArray, getEquipmentAndArmor
|
||||
}
|
||||
|
||||
if (attacker instanceof EntityHuman) {
|
||||
a(EnchantmentManager.c, user.getItemInMainHand()); // PAIL: applyEnchantmentModifier
|
||||
}
|
||||
|
||||
// Akarin Start - remove references to entity objects to avoid memory leaks
|
||||
c.b = null;
|
||||
c.a = null;
|
||||
// SAkarin end
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public static void b(EntityLiving user, Entity target) { // PAIL: applyArthropodEnchantments
|
||||
EnchantmentManager.d.a = user;
|
||||
EnchantmentManager.d.b = target;
|
||||
if (user != null) {
|
||||
a(EnchantmentManager.d, user.aQ()); // PAIL: applyEnchantmentModifierArray, getEquipmentAndArmor
|
||||
}
|
||||
|
||||
if (user instanceof EntityHuman) {
|
||||
a(EnchantmentManager.d, user.getItemInMainHand()); // PAIL: applyEnchantmentModifier
|
||||
}
|
||||
|
||||
// Akarin Start - remove references to entity objects to avoid memory leaks
|
||||
d.a = null;
|
||||
d.b = null;
|
||||
// Akarin end
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
import com.destroystokyo.paper.exception.ServerInternalException;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Lock for event (safety issue)
|
||||
*/
|
||||
public class ChunkProviderServer implements IChunkProvider {
|
||||
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
public final LongSet unloadQueue = new LongOpenHashSet();
|
||||
public final ChunkGenerator<?> chunkGenerator;
|
||||
public final IChunkLoader chunkLoader; // PAIL
|
||||
// Paper start - chunk save stats
|
||||
private long lastQueuedSaves = 0L; // Paper
|
||||
private long lastProcessedSaves = 0L; // Paper
|
||||
private long lastSaveStatPrinted = System.currentTimeMillis();
|
||||
// Paper end
|
||||
public final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192));
|
||||
private Chunk lastChunk;
|
||||
private final ChunkTaskScheduler chunkScheduler;
|
||||
final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; // Paper
|
||||
public final WorldServer world;
|
||||
final IAsyncTaskHandler asyncTaskHandler; // Paper
|
||||
|
||||
public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) {
|
||||
this.world = worldserver;
|
||||
this.chunkLoader = ichunkloader;
|
||||
this.chunkGenerator = chunkgenerator;
|
||||
this.asyncTaskHandler = iasynctaskhandler;
|
||||
this.chunkScheduler = new ChunkTaskScheduler(0, worldserver, chunkgenerator, ichunkloader, iasynctaskhandler); // CraftBukkit - very buggy, broken in lots of __subtle__ ways. Same goes for async chunk loading. Also Bukkit API / plugins can't handle async events at all anyway.
|
||||
this.batchScheduler = new SchedulerBatch(this.chunkScheduler);
|
||||
}
|
||||
|
||||
public Collection<Chunk> a() {
|
||||
return this.chunks.values();
|
||||
}
|
||||
|
||||
public void unload(Chunk chunk) {
|
||||
if (this.world.worldProvider.a(chunk.locX, chunk.locZ)) {
|
||||
this.unloadQueue.add(ChunkCoordIntPair.a(chunk.locX, chunk.locZ));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void b() {
|
||||
ObjectIterator objectiterator = this.chunks.values().iterator();
|
||||
|
||||
while (objectiterator.hasNext()) {
|
||||
Chunk chunk = (Chunk) objectiterator.next();
|
||||
|
||||
this.unload(chunk);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(int i, int j) {
|
||||
this.unloadQueue.remove(ChunkCoordIntPair.a(i, j));
|
||||
}
|
||||
|
||||
// Paper start - defaults if Async Chunks is not enabled
|
||||
boolean chunkGoingToExists(int x, int z) {
|
||||
final long k = ChunkCoordIntPair.asLong(x, z);
|
||||
return chunkScheduler.progressCache.containsKey(k);
|
||||
}
|
||||
public void bumpPriority(ChunkCoordIntPair coords) {
|
||||
// do nothing, override in async
|
||||
}
|
||||
|
||||
public List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
|
||||
List<ChunkCoordIntPair> list = com.google.common.collect.Lists.newArrayList();
|
||||
|
||||
for (int r = 1; r <= radius; r++) {
|
||||
int x = -r;
|
||||
int z = r;
|
||||
list.add(new ChunkCoordIntPair(blockposition.getX(), blockposition.getZ()));
|
||||
// Iterates the edge of half of the box; then negates for other half.
|
||||
while (x <= r && z > -r) {
|
||||
list.add(new ChunkCoordIntPair(blockposition.getX() + x, blockposition.getZ() + z));
|
||||
list.add(new ChunkCoordIntPair(blockposition.getX() - x, blockposition.getZ() - z));
|
||||
|
||||
if (x < r) {
|
||||
x++;
|
||||
} else {
|
||||
z--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Chunk getChunkAt(int x, int z, boolean load, boolean gen, Consumer<Chunk> consumer) {
|
||||
return getChunkAt(x, z, load, gen, false, consumer);
|
||||
}
|
||||
public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
|
||||
Chunk chunk = getChunkAt(x, z, load, gen);
|
||||
if (consumer != null) {
|
||||
consumer.accept(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@Nullable
|
||||
public Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) {
|
||||
IChunkLoader ichunkloader = this.chunkLoader;
|
||||
Chunk chunk;
|
||||
// Paper start - do already loaded checks before synchronize
|
||||
long k = ChunkCoordIntPair.a(i, j);
|
||||
chunk = (Chunk) this.chunks.get(k);
|
||||
if (chunk != null) {
|
||||
//this.lastChunk = chunk; // Paper remove vanilla lastChunk
|
||||
return chunk;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
synchronized (this.chunkLoader) {
|
||||
// Paper start - remove vanilla lastChunk, we do it more accurately
|
||||
/* if (this.lastChunk != null && this.lastChunk.locX == i && this.lastChunk.locZ == j) {
|
||||
return this.lastChunk;
|
||||
}*/ // Paper end
|
||||
|
||||
// Paper start - move up
|
||||
//long k = ChunkCoordIntPair.a(i, j);
|
||||
|
||||
/*chunk = (Chunk) this.chunks.get(k);
|
||||
if (chunk != null) {
|
||||
//this.lastChunk = chunk; // Paper remove vanilla lastChunk
|
||||
return chunk;
|
||||
}*/
|
||||
// Paper end
|
||||
|
||||
if (flag) {
|
||||
try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) { // Paper
|
||||
// CraftBukkit - decompile error
|
||||
chunk = this.chunkLoader.a(this.world, i, j, (chunk1) -> {
|
||||
chunk1.setLastSaved(this.world.getTime());
|
||||
this.chunks.put(ChunkCoordIntPair.a(i, j), chunk1);
|
||||
});
|
||||
} catch (Exception exception) {
|
||||
ChunkProviderServer.a.error("Couldn\'t load chunk", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk != null) {
|
||||
this.asyncTaskHandler.postToMainThread(chunk::addEntities);
|
||||
return chunk;
|
||||
} else if (flag1) {
|
||||
try (co.aikar.timings.Timing timing = world.timings.chunkGeneration.startTiming()) { // Paper
|
||||
this.batchScheduler.b();
|
||||
this.batchScheduler.a(new ChunkCoordIntPair(i, j));
|
||||
CompletableFuture<ProtoChunk> completablefuture = this.batchScheduler.c(); // CraftBukkit - decompile error
|
||||
|
||||
return (Chunk) completablefuture.thenApply(this::a).join();
|
||||
} catch (RuntimeException runtimeexception) {
|
||||
throw this.a(i, j, (Throwable) runtimeexception);
|
||||
}
|
||||
// finally { world.timings.syncChunkLoadTimer.stopTiming(); } // Spigot // Paper
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
public Chunk generateChunk(int x, int z) {
|
||||
try {
|
||||
this.batchScheduler.b();
|
||||
ChunkCoordIntPair pos = new ChunkCoordIntPair(x, z);
|
||||
this.chunkScheduler.forcePolluteCache(pos);
|
||||
this.batchScheduler.a(pos);
|
||||
CompletableFuture<ProtoChunk> completablefuture = this.batchScheduler.c();
|
||||
|
||||
return (Chunk) completablefuture.thenApply(this::a).join();
|
||||
} catch (RuntimeException runtimeexception) {
|
||||
throw this.a(x, z, (Throwable) runtimeexception);
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
public IChunkAccess a(int i, int j, boolean flag) {
|
||||
Chunk chunk = this.getChunkAt(i, j, true, false);
|
||||
|
||||
return (IChunkAccess) (chunk != null ? chunk : (IChunkAccess) this.chunkScheduler.b(new ChunkCoordIntPair(i, j), flag));
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { return a(iterable, consumer).thenCompose(protoChunk -> null); } // Paper - overriden in async chunk provider
|
||||
private CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { // Paper - mark private, use above method
|
||||
this.batchScheduler.b();
|
||||
Iterator iterator = iterable.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator.next();
|
||||
Chunk chunk = this.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, true, false);
|
||||
|
||||
if (chunk != null) {
|
||||
consumer.accept(chunk);
|
||||
} else {
|
||||
this.batchScheduler.a(chunkcoordintpair).thenApply(this::a).thenAccept(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
return this.batchScheduler.c();
|
||||
}
|
||||
|
||||
ReportedException generateChunkError(int i, int j, Throwable throwable) { return a(i, j, throwable); } // Paper - OBFHELPER
|
||||
private ReportedException a(int i, int j, 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(ChunkCoordIntPair.a(i, j)));
|
||||
crashreportsystemdetails.a("Generator", (Object) this.chunkGenerator);
|
||||
return new ReportedException(crashreport);
|
||||
}
|
||||
|
||||
private Chunk a(IChunkAccess ichunkaccess) {
|
||||
ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos();
|
||||
int i = chunkcoordintpair.x;
|
||||
int j = chunkcoordintpair.z;
|
||||
long k = ChunkCoordIntPair.a(i, j);
|
||||
Long2ObjectMap long2objectmap = this.chunks;
|
||||
Chunk chunk;
|
||||
|
||||
Akari.eventLock.lock(); // Akarin
|
||||
try { // Akarin
|
||||
synchronized (this.chunks) {
|
||||
Chunk chunk1 = (Chunk) this.chunks.get(k);
|
||||
|
||||
if (chunk1 != null) {
|
||||
return chunk1;
|
||||
}
|
||||
|
||||
if (ichunkaccess instanceof Chunk) {
|
||||
chunk = (Chunk) ichunkaccess;
|
||||
} else {
|
||||
if (!(ichunkaccess instanceof ProtoChunk)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
chunk = new Chunk(this.world, (ProtoChunk) ichunkaccess, i, j);
|
||||
}
|
||||
|
||||
this.chunks.put(k, chunk);
|
||||
//this.lastChunk = chunk; // Paper
|
||||
}
|
||||
} finally { Akari.eventLock.unlock(); } // Akarin
|
||||
|
||||
this.asyncTaskHandler.postToMainThread(chunk::addEntities);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public void saveChunk(IChunkAccess ichunkaccess, boolean unloaded) { // Spigot
|
||||
try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) { // Paper - Timings
|
||||
ichunkaccess.setLastSaved(this.world.getTime());
|
||||
this.chunkLoader.saveChunk(this.world, ichunkaccess, 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;
|
||||
|
||||
this.chunkScheduler.a(() -> {
|
||||
return true;
|
||||
});
|
||||
IChunkLoader ichunkloader = this.chunkLoader;
|
||||
|
||||
synchronized (this.chunkLoader) {
|
||||
ObjectIterator objectiterator = this.chunks.values().iterator();
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Paper end
|
||||
while (objectiterator.hasNext()) {
|
||||
Chunk chunk = (Chunk) objectiterator.next();
|
||||
|
||||
if (chunk.c(flag)) {
|
||||
this.saveChunk(chunk, false); // Spigot
|
||||
chunk.a(false);
|
||||
++i;
|
||||
if (!flag && i >= world.paperConfig.maxAutoSaveChunksPerTick) { // Spigot - // Paper - Incremental Auto Save - cap max
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// Paper start - we do not need to wait for chunk generations to finish on close
|
||||
/*try {
|
||||
this.batchScheduler.a();
|
||||
} catch (InterruptedException interruptedexception) {
|
||||
ChunkProviderServer.a.error("Couldn\'t stop taskManager", interruptedexception);
|
||||
}*/
|
||||
// Paper end
|
||||
|
||||
}
|
||||
|
||||
public void c() {
|
||||
IChunkLoader ichunkloader = this.chunkLoader;
|
||||
|
||||
synchronized (this.chunkLoader) {
|
||||
this.chunkLoader.b();
|
||||
}
|
||||
}
|
||||
|
||||
private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.96; // Spigot
|
||||
|
||||
public boolean unloadChunks(BooleanSupplier booleansupplier) {
|
||||
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 longiterator = this.unloadQueue.iterator();
|
||||
|
||||
while (longiterator.hasNext()) { // Spigot
|
||||
Long olong = (Long) longiterator.next();
|
||||
longiterator.remove(); // Spigot
|
||||
IChunkLoader ichunkloader = this.chunkLoader;
|
||||
|
||||
synchronized (this.chunkLoader) {
|
||||
Chunk chunk = (Chunk) this.chunks.get(olong);
|
||||
|
||||
if (chunk != null) {
|
||||
// CraftBukkit start - move unload logic to own method
|
||||
if (!unloadChunk(chunk, true)) {
|
||||
continue;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
// Spigot start
|
||||
if (!booleansupplier.getAsBoolean() && 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.chunkScheduler.a(booleansupplier);
|
||||
}
|
||||
|
||||
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.chunks.get(chunk.chunkKey); // Paper
|
||||
if (neighbor != null) {
|
||||
neighbor.setNeighborUnloaded(-x, -z);
|
||||
chunk.setNeighborUnloaded(x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Moved from unloadChunks above
|
||||
synchronized (this.chunkLoader) {
|
||||
chunk.removeEntities();
|
||||
if (save) {
|
||||
this.saveChunk(chunk, true); // Spigot
|
||||
}
|
||||
this.chunks.remove(chunk.chunkKey);
|
||||
// this.lastChunk = null; // Paper
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
public boolean d() {
|
||||
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);
|
||||
}
|
||||
|
||||
public int a(World world, boolean flag, boolean flag1) {
|
||||
return this.chunkGenerator.a(world, flag, flag1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockPosition a(World world, String s, BlockPosition blockposition, int i, boolean flag) {
|
||||
return this.chunkGenerator.findNearestMapFeature(world, s, blockposition, i, flag);
|
||||
}
|
||||
|
||||
public ChunkGenerator<?> getChunkGenerator() {
|
||||
return this.chunkGenerator;
|
||||
}
|
||||
|
||||
public int g() {
|
||||
return this.chunks.size();
|
||||
}
|
||||
|
||||
public boolean isLoaded(int i, int j) {
|
||||
return this.chunks.containsKey(ChunkCoordIntPair.a(i, j));
|
||||
}
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Expose private members<br>
|
||||
* @author cakoyo
|
||||
*/
|
||||
public class EnchantmentManager {
|
||||
|
||||
public static final EnchantmentManager.EnchantmentModifierProtection a = new EnchantmentManager.EnchantmentModifierProtection(null); // Akarin - private -> public
|
||||
private static final EnchantmentManager.EnchantmentModifierDamage b = new EnchantmentManager.EnchantmentModifierDamage(null);
|
||||
public static final EnchantmentManager.EnchantmentModifierThorns c = new EnchantmentManager.EnchantmentModifierThorns(null); // Akarin - private -> public
|
||||
public static final EnchantmentManager.EnchantmentModifierArthropods d = new EnchantmentManager.EnchantmentModifierArthropods(null); // Akarin - private -> public
|
||||
|
||||
public static int getEnchantmentLevel(Enchantment enchantment, ItemStack itemstack) {
|
||||
if (itemstack.isEmpty()) {
|
||||
return 0;
|
||||
} else {
|
||||
NBTTagList nbttaglist = itemstack.getEnchantments();
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
NBTTagCompound nbttagcompound = nbttaglist.get(i);
|
||||
Enchantment enchantment1 = Enchantment.c(nbttagcompound.getShort("id"));
|
||||
short short0 = nbttagcompound.getShort("lvl");
|
||||
|
||||
if (enchantment1 == enchantment) {
|
||||
return short0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Enchantment, Integer> a(ItemStack itemstack) {
|
||||
LinkedHashMap linkedhashmap = Maps.newLinkedHashMap();
|
||||
NBTTagList nbttaglist = itemstack.getItem() == Items.ENCHANTED_BOOK ? ItemEnchantedBook.h(itemstack) : itemstack.getEnchantments();
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
NBTTagCompound nbttagcompound = nbttaglist.get(i);
|
||||
Enchantment enchantment = Enchantment.c(nbttagcompound.getShort("id"));
|
||||
short short0 = nbttagcompound.getShort("lvl");
|
||||
|
||||
linkedhashmap.put(enchantment, Integer.valueOf(short0));
|
||||
}
|
||||
|
||||
return linkedhashmap;
|
||||
}
|
||||
|
||||
public static void a(Map<Enchantment, Integer> map, ItemStack itemstack) {
|
||||
NBTTagList nbttaglist = new NBTTagList();
|
||||
Iterator iterator = map.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entry entry = (Entry) iterator.next();
|
||||
Enchantment enchantment = (Enchantment) entry.getKey();
|
||||
|
||||
if (enchantment != null) {
|
||||
int i = ((Integer) entry.getValue()).intValue();
|
||||
NBTTagCompound nbttagcompound = new NBTTagCompound();
|
||||
|
||||
nbttagcompound.setShort("id", (short) Enchantment.getId(enchantment));
|
||||
nbttagcompound.setShort("lvl", (short) i);
|
||||
nbttaglist.add(nbttagcompound);
|
||||
if (itemstack.getItem() == Items.ENCHANTED_BOOK) {
|
||||
ItemEnchantedBook.a(itemstack, new WeightedRandomEnchant(enchantment, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nbttaglist.isEmpty()) {
|
||||
if (itemstack.hasTag()) {
|
||||
itemstack.getTag().remove("ench");
|
||||
}
|
||||
} else if (itemstack.getItem() != Items.ENCHANTED_BOOK) {
|
||||
itemstack.a("ench", nbttaglist);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void a(EnchantmentManager.EnchantmentModifier enchantmentmanager_enchantmentmodifier, ItemStack itemstack) {
|
||||
if (!itemstack.isEmpty()) {
|
||||
NBTTagList nbttaglist = itemstack.getEnchantments();
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
short short0 = nbttaglist.get(i).getShort("id");
|
||||
short short1 = nbttaglist.get(i).getShort("lvl");
|
||||
|
||||
if (Enchantment.c(short0) != null) {
|
||||
enchantmentmanager_enchantmentmodifier.a(Enchantment.c(short0), short1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void a(EnchantmentManager.EnchantmentModifier enchantmentmanager_enchantmentmodifier, Iterable<ItemStack> iterable) {
|
||||
Iterator iterator = iterable.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ItemStack itemstack = (ItemStack) iterator.next();
|
||||
|
||||
a(enchantmentmanager_enchantmentmodifier, itemstack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static int a(Iterable<ItemStack> iterable, DamageSource damagesource) {
|
||||
EnchantmentManager.a.a = 0;
|
||||
EnchantmentManager.a.b = damagesource;
|
||||
a(EnchantmentManager.a, iterable);
|
||||
return EnchantmentManager.a.a;
|
||||
}
|
||||
|
||||
public static float a(ItemStack itemstack, EnumMonsterType enummonstertype) {
|
||||
EnchantmentManager.b.a = 0.0F;
|
||||
EnchantmentManager.b.b = enummonstertype;
|
||||
a(EnchantmentManager.b, itemstack);
|
||||
return EnchantmentManager.b.a;
|
||||
}
|
||||
|
||||
public static float a(EntityLiving entityliving) {
|
||||
int i = a(Enchantments.r, entityliving);
|
||||
|
||||
return i > 0 ? EnchantmentSweeping.e(i) : 0.0F;
|
||||
}
|
||||
|
||||
public static void a(EntityLiving entityliving, Entity entity) {
|
||||
EnchantmentManager.c.b = entity;
|
||||
EnchantmentManager.c.a = entityliving;
|
||||
if (entityliving != null) {
|
||||
a(EnchantmentManager.c, entityliving.aQ());
|
||||
}
|
||||
|
||||
if (entity instanceof EntityHuman) {
|
||||
a(EnchantmentManager.c, entityliving.getItemInMainHand());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void b(EntityLiving entityliving, Entity entity) {
|
||||
EnchantmentManager.d.a = entityliving;
|
||||
EnchantmentManager.d.b = entity;
|
||||
if (entityliving != null) {
|
||||
a(EnchantmentManager.d, entityliving.aQ());
|
||||
}
|
||||
|
||||
if (entityliving instanceof EntityHuman) {
|
||||
a(EnchantmentManager.d, entityliving.getItemInMainHand());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static int a(Enchantment enchantment, EntityLiving entityliving) {
|
||||
List list = enchantment.a(entityliving);
|
||||
|
||||
if (list == null) {
|
||||
return 0;
|
||||
} else {
|
||||
int i = 0;
|
||||
Iterator iterator = list.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ItemStack itemstack = (ItemStack) iterator.next();
|
||||
int j = getEnchantmentLevel(enchantment, itemstack);
|
||||
|
||||
if (j > i) {
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
public static int b(EntityLiving entityliving) {
|
||||
return a(Enchantments.KNOCKBACK, entityliving);
|
||||
}
|
||||
|
||||
public static int getFireAspectEnchantmentLevel(EntityLiving entityliving) {
|
||||
return a(Enchantments.FIRE_ASPECT, entityliving);
|
||||
}
|
||||
|
||||
public static int getOxygenEnchantmentLevel(EntityLiving entityliving) {
|
||||
return a(Enchantments.OXYGEN, entityliving);
|
||||
}
|
||||
|
||||
public static int e(EntityLiving entityliving) {
|
||||
return a(Enchantments.DEPTH_STRIDER, entityliving);
|
||||
}
|
||||
|
||||
public static int getDigSpeedEnchantmentLevel(EntityLiving entityliving) {
|
||||
return a(Enchantments.DIG_SPEED, entityliving);
|
||||
}
|
||||
|
||||
public static int b(ItemStack itemstack) {
|
||||
return getEnchantmentLevel(Enchantments.LUCK, itemstack);
|
||||
}
|
||||
|
||||
public static int c(ItemStack itemstack) {
|
||||
return getEnchantmentLevel(Enchantments.LURE, itemstack);
|
||||
}
|
||||
|
||||
public static int g(EntityLiving entityliving) {
|
||||
return a(Enchantments.LOOT_BONUS_MOBS, entityliving);
|
||||
}
|
||||
|
||||
public static boolean h(EntityLiving entityliving) {
|
||||
return a(Enchantments.WATER_WORKER, entityliving) > 0;
|
||||
}
|
||||
|
||||
public static boolean i(EntityLiving entityliving) {
|
||||
return a(Enchantments.j, entityliving) > 0;
|
||||
}
|
||||
|
||||
public static boolean d(ItemStack itemstack) {
|
||||
return getEnchantmentLevel(Enchantments.k, itemstack) > 0;
|
||||
}
|
||||
|
||||
public static boolean shouldNotDrop(ItemStack itemstack) {
|
||||
return getEnchantmentLevel(Enchantments.D, itemstack) > 0;
|
||||
}
|
||||
|
||||
public static ItemStack getRandomEquippedItemWithEnchant(Enchantment enchantment, EntityLiving entityliving) { return b(enchantment, entityliving); } // Paper - OBFHELPER
|
||||
public static ItemStack b(Enchantment enchantment, EntityLiving entityliving) {
|
||||
List list = enchantment.a(entityliving);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return ItemStack.a;
|
||||
} else {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
Iterator iterator = list.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ItemStack itemstack = (ItemStack) iterator.next();
|
||||
|
||||
if (!itemstack.isEmpty() && getEnchantmentLevel(enchantment, itemstack) > 0) {
|
||||
arraylist.add(itemstack);
|
||||
}
|
||||
}
|
||||
|
||||
return arraylist.isEmpty() ? ItemStack.a : (ItemStack) arraylist.get(entityliving.getRandom().nextInt(arraylist.size()));
|
||||
}
|
||||
}
|
||||
|
||||
public static int a(Random random, int i, int j, ItemStack itemstack) {
|
||||
Item item = itemstack.getItem();
|
||||
int k = item.c();
|
||||
|
||||
if (k <= 0) {
|
||||
return 0;
|
||||
} else {
|
||||
if (j > 15) {
|
||||
j = 15;
|
||||
}
|
||||
|
||||
int l = random.nextInt(8) + 1 + (j >> 1) + random.nextInt(j + 1);
|
||||
|
||||
return i == 0 ? Math.max(l / 3, 1) : (i == 1 ? l * 2 / 3 + 1 : Math.max(l, j * 2));
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack a(Random random, ItemStack itemstack, int i, boolean flag) {
|
||||
List list = b(random, itemstack, i, flag);
|
||||
boolean flag1 = itemstack.getItem() == Items.BOOK;
|
||||
|
||||
if (flag1) {
|
||||
itemstack = new ItemStack(Items.ENCHANTED_BOOK);
|
||||
}
|
||||
|
||||
Iterator iterator = list.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) iterator.next();
|
||||
|
||||
if (flag1) {
|
||||
ItemEnchantedBook.a(itemstack, weightedrandomenchant);
|
||||
} else {
|
||||
itemstack.addEnchantment(weightedrandomenchant.enchantment, weightedrandomenchant.level);
|
||||
}
|
||||
}
|
||||
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
public static List<WeightedRandomEnchant> b(Random random, ItemStack itemstack, int i, boolean flag) {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
Item item = itemstack.getItem();
|
||||
int j = item.c();
|
||||
|
||||
if (j <= 0) {
|
||||
return arraylist;
|
||||
} else {
|
||||
i += 1 + random.nextInt(j / 4 + 1) + random.nextInt(j / 4 + 1);
|
||||
float f = (random.nextFloat() + random.nextFloat() - 1.0F) * 0.15F;
|
||||
|
||||
i = MathHelper.clamp(Math.round(i + i * f), 1, Integer.MAX_VALUE);
|
||||
List list = a(i, itemstack, flag);
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
arraylist.add(WeightedRandom.a(random, list));
|
||||
|
||||
while (random.nextInt(50) <= i) {
|
||||
a(list, (WeightedRandomEnchant) SystemUtils.a(arraylist));
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
arraylist.add(WeightedRandom.a(random, list));
|
||||
i /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return arraylist;
|
||||
}
|
||||
}
|
||||
|
||||
public static void a(List<WeightedRandomEnchant> list, WeightedRandomEnchant weightedrandomenchant) {
|
||||
Iterator iterator = list.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
if (!weightedrandomenchant.enchantment.c(((WeightedRandomEnchant) iterator.next()).enchantment)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static List<WeightedRandomEnchant> a(int i, ItemStack itemstack, boolean flag) {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
Item item = itemstack.getItem();
|
||||
boolean flag1 = itemstack.getItem() == Items.BOOK;
|
||||
Iterator iterator = Enchantment.enchantments.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Enchantment enchantment = (Enchantment) iterator.next();
|
||||
|
||||
if ((!enchantment.isTreasure() || flag) && (enchantment.itemTarget.canEnchant(item) || flag1)) {
|
||||
for (int j = enchantment.getMaxLevel(); j > enchantment.getStartLevel() - 1; --j) {
|
||||
if (i >= enchantment.a(j) && i <= enchantment.b(j)) {
|
||||
arraylist.add(new WeightedRandomEnchant(enchantment, j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arraylist;
|
||||
}
|
||||
|
||||
public static final class EnchantmentModifierArthropods implements EnchantmentManager.EnchantmentModifier { // Akarin - private -> public
|
||||
|
||||
public EntityLiving a;
|
||||
public Entity b;
|
||||
|
||||
private EnchantmentModifierArthropods() {}
|
||||
|
||||
@Override
|
||||
public void a(Enchantment enchantment, int i) {
|
||||
enchantment.a(this.a, this.b, i);
|
||||
}
|
||||
|
||||
EnchantmentModifierArthropods(Object object) {
|
||||
this();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EnchantmentModifierThorns implements EnchantmentManager.EnchantmentModifier { // Akarin - private -> public
|
||||
|
||||
public EntityLiving a;
|
||||
public Entity b;
|
||||
|
||||
private EnchantmentModifierThorns() {}
|
||||
|
||||
@Override
|
||||
public void a(Enchantment enchantment, int i) {
|
||||
enchantment.b(this.a, this.b, i);
|
||||
}
|
||||
|
||||
EnchantmentModifierThorns(Object object) {
|
||||
this();
|
||||
}
|
||||
}
|
||||
|
||||
static final class EnchantmentModifierDamage implements EnchantmentManager.EnchantmentModifier {
|
||||
|
||||
public float a;
|
||||
public EnumMonsterType b;
|
||||
|
||||
private EnchantmentModifierDamage() {}
|
||||
|
||||
@Override
|
||||
public void a(Enchantment enchantment, int i) {
|
||||
this.a += enchantment.a(i, this.b);
|
||||
}
|
||||
|
||||
EnchantmentModifierDamage(Object object) {
|
||||
this();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EnchantmentModifierProtection implements EnchantmentManager.EnchantmentModifier { // Akarin - private -> public
|
||||
|
||||
public int a;
|
||||
public DamageSource b;
|
||||
|
||||
private EnchantmentModifierProtection() {}
|
||||
|
||||
@Override
|
||||
public void a(Enchantment enchantment, int i) {
|
||||
this.a += enchantment.a(i, this.b);
|
||||
}
|
||||
|
||||
EnchantmentModifierProtection(Object object) {
|
||||
this();
|
||||
}
|
||||
}
|
||||
|
||||
public interface EnchantmentModifier { // Akarin - private -> public
|
||||
|
||||
void a(Enchantment enchantment, int i);
|
||||
}
|
||||
}
|
||||
3225
sources/src/main/java/net/minecraft/server/Entity.java
Normal file
3225
sources/src/main/java/net/minecraft/server/Entity.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
333
sources/src/main/java/net/minecraft/server/EntityTracker.java
Normal file
333
sources/src/main/java/net/minecraft/server/EntityTracker.java
Normal file
@@ -0,0 +1,333 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Made collections and entry accesses thread-safe (safety issue)
|
||||
*/
|
||||
public class EntityTracker {
|
||||
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
private final WorldServer world;
|
||||
private final Set<EntityTrackerEntry> c = Sets.newHashSet();
|
||||
public final IntHashMap<EntityTrackerEntry> trackedEntities = new IntHashMap();
|
||||
private int e;
|
||||
|
||||
public EntityTracker(WorldServer worldserver) {
|
||||
this.world = worldserver;
|
||||
this.e = PlayerChunkMap.getFurthestViewableBlock(worldserver.spigotConfig.viewDistance); // Spigot
|
||||
}
|
||||
|
||||
public static long a(double d0) {
|
||||
return MathHelper.d(d0 * 4096.0D);
|
||||
}
|
||||
|
||||
public void track(Entity entity) {
|
||||
if (entity instanceof EntityPlayer) {
|
||||
this.addEntity(entity, 512, 2);
|
||||
EntityPlayer entityplayer = (EntityPlayer) entity;
|
||||
Iterator iterator = this.c.iterator();
|
||||
// entriesLock.writeLock().lock(); // Akarin - locked in EntityPlayer
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
if (entitytrackerentry.b() != entityplayer) {
|
||||
entitytrackerentry.updatePlayer(entityplayer);
|
||||
}
|
||||
}
|
||||
// entriesLock.writeLock().unlock(); // Akarin - locked in EntityPlayer
|
||||
} else if (entity instanceof EntityFishingHook) {
|
||||
this.addEntity(entity, 64, 5, true);
|
||||
} else if (entity instanceof EntityArrow) {
|
||||
this.addEntity(entity, 64, 20, false);
|
||||
} else if (entity instanceof EntitySmallFireball) {
|
||||
this.addEntity(entity, 64, 10, false);
|
||||
} else if (entity instanceof EntityFireball) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntitySnowball) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityLlamaSpit) {
|
||||
this.addEntity(entity, 64, 10, false);
|
||||
} else if (entity instanceof EntityEnderPearl) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityEnderSignal) {
|
||||
this.addEntity(entity, 64, 4, true);
|
||||
} else if (entity instanceof EntityEgg) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityPotion) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityThrownExpBottle) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityFireworks) {
|
||||
this.addEntity(entity, 64, 10, true);
|
||||
} else if (entity instanceof EntityItem) {
|
||||
this.addEntity(entity, 64, 20, true);
|
||||
} else if (entity instanceof EntityMinecartAbstract) {
|
||||
this.addEntity(entity, 80, 3, true);
|
||||
} else if (entity instanceof EntityBoat) {
|
||||
this.addEntity(entity, 80, 3, true);
|
||||
} else if (entity instanceof EntitySquid) {
|
||||
this.addEntity(entity, 64, 3, true);
|
||||
} else if (entity instanceof EntityWither) {
|
||||
this.addEntity(entity, 80, 3, false);
|
||||
} else if (entity instanceof EntityShulkerBullet) {
|
||||
this.addEntity(entity, 80, 3, true);
|
||||
} else if (entity instanceof EntityBat) {
|
||||
this.addEntity(entity, 80, 3, false);
|
||||
} else if (entity instanceof EntityEnderDragon) {
|
||||
this.addEntity(entity, 160, 3, true);
|
||||
} else if (entity instanceof IAnimal) {
|
||||
this.addEntity(entity, 80, 3, true);
|
||||
} else if (entity instanceof EntityTNTPrimed) {
|
||||
this.addEntity(entity, 160, 10, true);
|
||||
} else if (entity instanceof EntityFallingBlock) {
|
||||
this.addEntity(entity, 160, 20, true);
|
||||
} else if (entity instanceof EntityHanging) {
|
||||
this.addEntity(entity, 160, Integer.MAX_VALUE, false);
|
||||
} else if (entity instanceof EntityArmorStand) {
|
||||
this.addEntity(entity, 160, 3, true);
|
||||
} else if (entity instanceof EntityExperienceOrb) {
|
||||
this.addEntity(entity, 160, 20, true);
|
||||
} else if (entity instanceof EntityAreaEffectCloud) {
|
||||
this.addEntity(entity, 160, 10, true); // CraftBukkit
|
||||
} else if (entity instanceof EntityEnderCrystal) {
|
||||
this.addEntity(entity, 256, Integer.MAX_VALUE, false);
|
||||
} else if (entity instanceof EntityEvokerFangs) {
|
||||
this.addEntity(entity, 160, 2, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void addEntity(Entity entity, int i, int j) {
|
||||
this.addEntity(entity, i, j, false);
|
||||
}
|
||||
|
||||
public void addEntity(Entity entity, int originalRange, int j, boolean flag) { // Spigot
|
||||
org.spigotmc.AsyncCatcher.catchOp( "entity track"); // Spigot
|
||||
int i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, originalRange); // Spigot
|
||||
try {
|
||||
// entriesLock.writeLock().lock(); // Akarin - locked from track method
|
||||
if (this.trackedEntities.b(entity.getId())) {
|
||||
throw new IllegalStateException("Entity is already tracked!");
|
||||
}
|
||||
|
||||
EntityTrackerEntry entitytrackerentry = new EntityTrackerEntry(entity, i, this.e, j, flag);
|
||||
|
||||
this.c.add(entitytrackerentry);
|
||||
this.trackedEntities.a(entity.getId(), entitytrackerentry);
|
||||
entitytrackerentry.scanPlayers(this.world.players);
|
||||
// entriesLock.writeLock().unlock(); // Akarin - locked from track method
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport = CrashReport.a(throwable, "Adding entity to track");
|
||||
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity To Track");
|
||||
|
||||
crashreportsystemdetails.a("Tracking range", (Object) (i + " blocks"));
|
||||
crashreportsystemdetails.a("Update interval", () -> {
|
||||
String s = "Once per " + i + " ticks";
|
||||
|
||||
if (i == Integer.MAX_VALUE) {
|
||||
s = "Maximum (" + s + ")";
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
entity.appendEntityCrashDetails(crashreportsystemdetails);
|
||||
((EntityTrackerEntry) this.trackedEntities.get(entity.getId())).b().appendEntityCrashDetails(crashreport.a("Entity That Is Already Tracked"));
|
||||
|
||||
try {
|
||||
throw new ReportedException(crashreport);
|
||||
} catch (ReportedException reportedexception) {
|
||||
EntityTracker.a.error("\"Silently\" catching entity tracking error.", reportedexception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void untrackEntity(Entity entity) {
|
||||
org.spigotmc.AsyncCatcher.catchOp( "entity untrack"); // Spigot
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
if (entity instanceof EntityPlayer) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) entity;
|
||||
Iterator iterator = this.c.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.a(entityplayer);
|
||||
}
|
||||
}
|
||||
|
||||
EntityTrackerEntry entitytrackerentry1 = (EntityTrackerEntry) this.trackedEntities.d(entity.getId());
|
||||
|
||||
if (entitytrackerentry1 != null) {
|
||||
this.c.remove(entitytrackerentry1);
|
||||
entitytrackerentry1.a();
|
||||
}
|
||||
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public void updatePlayers() {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
Iterator iterator = this.c.iterator();
|
||||
world.timings.tracker1.startTiming(); // Spigot
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.track(this.world.players);
|
||||
if (entitytrackerentry.b) {
|
||||
Entity entity = entitytrackerentry.b();
|
||||
|
||||
if (entity instanceof EntityPlayer) {
|
||||
arraylist.add((EntityPlayer) entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
world.timings.tracker1.stopTiming(); // Spigot
|
||||
|
||||
world.timings.tracker2.startTiming(); // Spigot
|
||||
for (int i = 0; i < arraylist.size(); ++i) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) arraylist.get(i);
|
||||
Iterator iterator1 = this.c.iterator();
|
||||
|
||||
while (iterator1.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry1 = (EntityTrackerEntry) iterator1.next();
|
||||
|
||||
if (entitytrackerentry1.b() != entityplayer) {
|
||||
entitytrackerentry1.updatePlayer(entityplayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Akarin
|
||||
world.timings.tracker2.stopTiming(); // Spigot
|
||||
|
||||
}
|
||||
|
||||
public void updatePlayer(EntityPlayer entityplayer) { a(entityplayer); } // Paper - OBFHELPER
|
||||
public void a(EntityPlayer entityplayer) {
|
||||
Iterator iterator = this.c.iterator();
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
if (entitytrackerentry.b() == entityplayer) {
|
||||
entitytrackerentry.scanPlayers(this.world.players);
|
||||
} else {
|
||||
entitytrackerentry.updatePlayer(entityplayer);
|
||||
}
|
||||
}
|
||||
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public void a(Entity entity, Packet<?> packet) {
|
||||
EntityTrackerEntry entitytrackerentry; // Akarin
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
entitytrackerentry = (EntityTrackerEntry) this.trackedEntities.get(entity.getId());
|
||||
} // Akarin
|
||||
|
||||
if (entitytrackerentry != null) {
|
||||
entitytrackerentry.broadcast(packet);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sendPacketToEntity(Entity entity, Packet<?> packet) {
|
||||
EntityTrackerEntry entitytrackerentry; // Akarin
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
entitytrackerentry = (EntityTrackerEntry) this.trackedEntities.get(entity.getId());
|
||||
} // Akarin
|
||||
|
||||
if (entitytrackerentry != null) {
|
||||
entitytrackerentry.broadcastIncludingSelf(packet);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void untrackPlayer(EntityPlayer entityplayer) {
|
||||
Iterator iterator = this.c.iterator();
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.clear(entityplayer);
|
||||
}
|
||||
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public void a(EntityPlayer entityplayer, Chunk chunk) {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
ArrayList arraylist1 = Lists.newArrayList();
|
||||
Iterator iterator = this.c.iterator();
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
Entity entity = entitytrackerentry.b();
|
||||
|
||||
if (entity != entityplayer && entity.ae == chunk.locX && entity.ag == chunk.locZ) {
|
||||
entitytrackerentry.updatePlayer(entityplayer);
|
||||
if (entity instanceof EntityInsentient && ((EntityInsentient) entity).getLeashHolder() != null) {
|
||||
arraylist.add(entity);
|
||||
}
|
||||
|
||||
if (!entity.bP().isEmpty()) {
|
||||
arraylist1.add(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Akarin
|
||||
|
||||
Entity entity1;
|
||||
|
||||
if (!arraylist.isEmpty()) {
|
||||
iterator = arraylist.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
entity1 = (Entity) iterator.next();
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutAttachEntity(entity1, ((EntityInsentient) entity1).getLeashHolder()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!arraylist1.isEmpty()) {
|
||||
iterator = arraylist1.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
entity1 = (Entity) iterator.next();
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutMount(entity1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(int i) {
|
||||
this.e = (i - 1) * 16;
|
||||
Iterator iterator = this.c.iterator();
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.a(this.e);
|
||||
}
|
||||
|
||||
} // Akarin
|
||||
}
|
||||
}
|
||||
82
sources/src/main/java/net/minecraft/server/FileIOThread.java
Normal file
82
sources/src/main/java/net/minecraft/server/FileIOThread.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Multi-threaded chunk saving (performance)
|
||||
*/
|
||||
public class FileIOThread implements Runnable {
|
||||
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
private static final FileIOThread b = new FileIOThread();
|
||||
private final List<IAsyncChunkSaver> c = /*Collections.synchronizedList(Lists.newArrayList())*/ null; // Akarin - I don't think any plugin rely on this
|
||||
private volatile long d;
|
||||
private volatile long e;
|
||||
private volatile boolean f;
|
||||
|
||||
private FileIOThread() {
|
||||
// Thread thread = new Thread(this, "File IO Thread"); // Akarin
|
||||
|
||||
// thread.setUncaughtExceptionHandler(new ThreadNamedUncaughtExceptionHandler(FileIOThread.a)); // Akarin
|
||||
// thread.setPriority(1); // Akarin
|
||||
// thread.start(); // Akarin
|
||||
}
|
||||
|
||||
public static FileIOThread a() {
|
||||
return FileIOThread.b;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
this.c();
|
||||
}
|
||||
}
|
||||
|
||||
private void c() {
|
||||
for (int i = 0; i < this.c.size(); ++i) {
|
||||
IAsyncChunkSaver iasyncchunksaver = (IAsyncChunkSaver) this.c.get(i);
|
||||
boolean flag = iasyncchunksaver.a();
|
||||
|
||||
if (!flag) {
|
||||
this.c.remove(i--);
|
||||
++this.e;
|
||||
}
|
||||
|
||||
if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) { // Paper
|
||||
try {
|
||||
Thread.sleep(this.f ? 0L : 1L); // Paper
|
||||
} catch (InterruptedException interruptedexception) {
|
||||
interruptedexception.printStackTrace();
|
||||
}} // Paper
|
||||
}
|
||||
|
||||
if (this.c.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(25L);
|
||||
} catch (InterruptedException interruptedexception1) {
|
||||
interruptedexception1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(IAsyncChunkSaver iasyncchunksaver) {
|
||||
if (!this.c.contains(iasyncchunksaver)) {
|
||||
++this.d;
|
||||
this.c.add(iasyncchunksaver);
|
||||
}
|
||||
}
|
||||
|
||||
public void b() throws InterruptedException {
|
||||
this.f = true;
|
||||
|
||||
while (this.d != this.e) {
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
|
||||
this.f = false;
|
||||
}
|
||||
}
|
||||
99
sources/src/main/java/net/minecraft/server/ItemEnderEye.java
Normal file
99
sources/src/main/java/net/minecraft/server/ItemEnderEye.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add end portal disable feature (feature)
|
||||
*/
|
||||
public class ItemEnderEye extends Item {
|
||||
|
||||
public ItemEnderEye(Item.Info item_info) {
|
||||
super(item_info);
|
||||
}
|
||||
|
||||
public EnumInteractionResult a(ItemActionContext itemactioncontext) {
|
||||
World world = itemactioncontext.getWorld();
|
||||
BlockPosition blockposition = itemactioncontext.getClickPosition();
|
||||
IBlockData iblockdata = world.getType(blockposition);
|
||||
|
||||
if (iblockdata.getBlock() == Blocks.END_PORTAL_FRAME && !((Boolean) iblockdata.get(BlockEnderPortalFrame.EYE)).booleanValue()) {
|
||||
if (world.isClientSide) {
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
} else {
|
||||
IBlockData iblockdata1 = (IBlockData) iblockdata.set(BlockEnderPortalFrame.EYE, Boolean.valueOf(true));
|
||||
|
||||
Block.a(iblockdata, iblockdata1, world, blockposition);
|
||||
world.setTypeAndData(blockposition, iblockdata1, 2);
|
||||
world.updateAdjacentComparators(blockposition, Blocks.END_PORTAL_FRAME);
|
||||
itemactioncontext.getItemStack().subtract(1);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
double d0 = (double) ((float) blockposition.getX() + (5.0F + ItemEnderEye.i.nextFloat() * 6.0F) / 16.0F);
|
||||
double d1 = (double) ((float) blockposition.getY() + 0.8125F);
|
||||
double d2 = (double) ((float) blockposition.getZ() + (5.0F + ItemEnderEye.i.nextFloat() * 6.0F) / 16.0F);
|
||||
double d3 = 0.0D;
|
||||
double d4 = 0.0D;
|
||||
double d5 = 0.0D;
|
||||
|
||||
world.addParticle(Particles.M, d0, d1, d2, 0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
|
||||
world.a((EntityHuman) null, blockposition, SoundEffects.BLOCK_END_PORTAL_FRAME_FILL, SoundCategory.BLOCKS, 1.0F, 1.0F);
|
||||
if (AkarinGlobalConfig.disableEndPortalCreate) return EnumInteractionResult.SUCCESS; // Akarin
|
||||
ShapeDetector.ShapeDetectorCollection shapedetector_shapedetectorcollection = BlockEnderPortalFrame.d().a(world, blockposition);
|
||||
|
||||
if (shapedetector_shapedetectorcollection != null) {
|
||||
BlockPosition blockposition1 = shapedetector_shapedetectorcollection.a().a(-3, 0, -3);
|
||||
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
world.setTypeAndData(blockposition1.a(j, 0, k), Blocks.END_PORTAL.getBlockData(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
world.a(1038, blockposition1.a(1, 0, 1), 0);
|
||||
}
|
||||
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
}
|
||||
} else {
|
||||
return EnumInteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
|
||||
public InteractionResultWrapper<ItemStack> a(World world, EntityHuman entityhuman, EnumHand enumhand) {
|
||||
ItemStack itemstack = entityhuman.b(enumhand);
|
||||
MovingObjectPosition movingobjectposition = this.a(world, entityhuman, false);
|
||||
|
||||
if (movingobjectposition != null && movingobjectposition.type == MovingObjectPosition.EnumMovingObjectType.BLOCK && world.getType(movingobjectposition.a()).getBlock() == Blocks.END_PORTAL_FRAME) {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
} else {
|
||||
entityhuman.c(enumhand);
|
||||
if (!world.isClientSide) {
|
||||
BlockPosition blockposition = ((WorldServer) world).getChunkProviderServer().a(world, "Stronghold", new BlockPosition(entityhuman), 100, false);
|
||||
|
||||
if (blockposition != null) {
|
||||
EntityEnderSignal entityendersignal = new EntityEnderSignal(world, entityhuman.locX, entityhuman.locY + (double) (entityhuman.length / 2.0F), entityhuman.locZ);
|
||||
|
||||
entityendersignal.a(blockposition);
|
||||
world.addEntity(entityendersignal);
|
||||
if (entityhuman instanceof EntityPlayer) {
|
||||
CriterionTriggers.m.a((EntityPlayer) entityhuman, blockposition);
|
||||
}
|
||||
|
||||
world.a((EntityHuman) null, entityhuman.locX, entityhuman.locY, entityhuman.locZ, SoundEffects.ENTITY_ENDER_EYE_LAUNCH, SoundCategory.NEUTRAL, 0.5F, 0.4F / (ItemEnderEye.i.nextFloat() * 0.4F + 0.8F));
|
||||
world.a((EntityHuman) null, 1003, new BlockPosition(entityhuman), 0);
|
||||
if (!entityhuman.abilities.canInstantlyBuild) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
|
||||
entityhuman.b(StatisticList.ITEM_USED.b(this));
|
||||
return new InteractionResultWrapper(EnumInteractionResult.SUCCESS, itemstack);
|
||||
}
|
||||
}
|
||||
|
||||
return new InteractionResultWrapper(EnumInteractionResult.SUCCESS, itemstack);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
sources/src/main/java/net/minecraft/server/ItemMonsterEgg.java
Normal file
134
sources/src/main/java/net/minecraft/server/ItemMonsterEgg.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Restricted spawner modify (feature)
|
||||
*/
|
||||
public class ItemMonsterEgg extends Item {
|
||||
|
||||
private static final Map<EntityTypes<?>, ItemMonsterEgg> a = Maps.newIdentityHashMap();
|
||||
private final int b;
|
||||
private final int c;
|
||||
private final EntityTypes<?> d;
|
||||
|
||||
public ItemMonsterEgg(EntityTypes<?> entitytypes, int i, int j, Item.Info item_info) {
|
||||
super(item_info);
|
||||
this.d = entitytypes;
|
||||
this.b = i;
|
||||
this.c = j;
|
||||
ItemMonsterEgg.a.put(entitytypes, this);
|
||||
}
|
||||
|
||||
public EnumInteractionResult a(ItemActionContext itemactioncontext) {
|
||||
World world = itemactioncontext.getWorld();
|
||||
|
||||
if (world.isClientSide) {
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
} else {
|
||||
ItemStack itemstack = itemactioncontext.getItemStack();
|
||||
BlockPosition blockposition = itemactioncontext.getClickPosition();
|
||||
EnumDirection enumdirection = itemactioncontext.getClickedFace();
|
||||
IBlockData iblockdata = world.getType(blockposition);
|
||||
Block block = iblockdata.getBlock();
|
||||
|
||||
if (block == Blocks.SPAWNER && (AkarinGlobalConfig.allowSpawnerModify || itemactioncontext.getEntity().isCreativeAndOp())) { // Akarin
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntityMobSpawner) {
|
||||
MobSpawnerAbstract mobspawnerabstract = ((TileEntityMobSpawner) tileentity).getSpawner();
|
||||
EntityTypes entitytypes = this.b(itemstack.getTag());
|
||||
|
||||
if (entitytypes != null) {
|
||||
mobspawnerabstract.setMobName(entitytypes);
|
||||
tileentity.update();
|
||||
world.notify(blockposition, iblockdata, iblockdata, 3);
|
||||
}
|
||||
|
||||
itemstack.subtract(1);
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
BlockPosition blockposition1;
|
||||
|
||||
if (iblockdata.h(world, blockposition).b()) {
|
||||
blockposition1 = blockposition;
|
||||
} else {
|
||||
blockposition1 = blockposition.shift(enumdirection);
|
||||
}
|
||||
|
||||
EntityTypes entitytypes1 = this.b(itemstack.getTag());
|
||||
|
||||
if (entitytypes1 == null || entitytypes1.a(world, itemstack, itemactioncontext.getEntity(), blockposition1, true, !Objects.equals(blockposition, blockposition1) && enumdirection == EnumDirection.UP) != null) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
public InteractionResultWrapper<ItemStack> a(World world, EntityHuman entityhuman, EnumHand enumhand) {
|
||||
ItemStack itemstack = entityhuman.b(enumhand);
|
||||
|
||||
if (world.isClientSide) {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
} else {
|
||||
MovingObjectPosition movingobjectposition = this.a(world, entityhuman, true);
|
||||
|
||||
if (movingobjectposition != null && movingobjectposition.type == MovingObjectPosition.EnumMovingObjectType.BLOCK) {
|
||||
BlockPosition blockposition = movingobjectposition.a();
|
||||
|
||||
if (!(world.getType(blockposition).getBlock() instanceof BlockFluids)) {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
} else if (world.a(entityhuman, blockposition) && entityhuman.a(blockposition, movingobjectposition.direction, itemstack)) {
|
||||
EntityTypes entitytypes = this.b(itemstack.getTag());
|
||||
|
||||
if (entitytypes != null && entitytypes.a(world, itemstack, entityhuman, blockposition, false, false) != null) {
|
||||
if (!entityhuman.abilities.canInstantlyBuild) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
|
||||
entityhuman.b(StatisticList.ITEM_USED.b(this));
|
||||
return new InteractionResultWrapper(EnumInteractionResult.SUCCESS, itemstack);
|
||||
} else {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
}
|
||||
} else {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.FAIL, itemstack);
|
||||
}
|
||||
} else {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean a(@Nullable NBTTagCompound nbttagcompound, EntityTypes<?> entitytypes) {
|
||||
return Objects.equals(this.b(nbttagcompound), entitytypes);
|
||||
}
|
||||
|
||||
public static Iterable<ItemMonsterEgg> d() {
|
||||
return Iterables.unmodifiableIterable(ItemMonsterEgg.a.values());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EntityTypes<?> b(@Nullable NBTTagCompound nbttagcompound) {
|
||||
if (nbttagcompound != null && nbttagcompound.hasKeyOfType("EntityTag", 10)) {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("EntityTag");
|
||||
|
||||
if (nbttagcompound1.hasKeyOfType("id", 8)) {
|
||||
return EntityTypes.a(nbttagcompound1.getString("id"));
|
||||
}
|
||||
}
|
||||
|
||||
return this.d;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
||||
|
||||
import io.akarin.api.CheckedConcurrentLinkedQueue;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalEventLoopGroup;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.handler.timeout.TimeoutException;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.SecretKey;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -31,11 +28,8 @@ import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Add volatile to fields<br>
|
||||
* 2) Expose private members<br>
|
||||
* @author cakoyo
|
||||
* Akarin Changes Note
|
||||
* 1) Changes lock type to updatable lock (compatibility)
|
||||
*/
|
||||
public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
@@ -43,50 +37,35 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
public static final Marker a = MarkerManager.getMarker("NETWORK");
|
||||
public static final Marker b = MarkerManager.getMarker("NETWORK_PACKETS", NetworkManager.a);
|
||||
public static final AttributeKey<EnumProtocol> c = AttributeKey.valueOf("protocol");
|
||||
public static final LazyInitVar<NioEventLoopGroup> d = new LazyInitVar() {
|
||||
protected NioEventLoopGroup a() {
|
||||
return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object init() {
|
||||
return this.a();
|
||||
}
|
||||
};
|
||||
public static final LazyInitVar<EpollEventLoopGroup> e = new LazyInitVar() {
|
||||
protected EpollEventLoopGroup a() {
|
||||
return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object init() {
|
||||
return this.a();
|
||||
}
|
||||
};
|
||||
public static final LazyInitVar<LocalEventLoopGroup> f = new LazyInitVar() {
|
||||
protected LocalEventLoopGroup a() {
|
||||
return new LocalEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object init() {
|
||||
return this.a();
|
||||
}
|
||||
};
|
||||
public static final LazyInitVar<NioEventLoopGroup> d = new LazyInitVar(() -> {
|
||||
return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
|
||||
});
|
||||
public static final LazyInitVar<EpollEventLoopGroup> e = new LazyInitVar(() -> {
|
||||
return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
|
||||
});
|
||||
public static final LazyInitVar<DefaultEventLoopGroup> f = new LazyInitVar(() -> {
|
||||
return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
|
||||
});
|
||||
private final EnumProtocolDirection h;
|
||||
private final Queue<NetworkManager.QueuedPacket> i = new CheckedConcurrentLinkedQueue<NetworkManager.QueuedPacket>(); private final Queue<NetworkManager.QueuedPacket> getPacketQueue() { return this.i; } // Paper - Anti-Xray - OBFHELPER // Akarin
|
||||
private final ReentrantReadWriteLock j = new ReentrantReadWriteLock();
|
||||
private final Queue<NetworkManager.QueuedPacket> i = new io.akarin.api.internal.utils.CheckedConcurrentLinkedQueue<>(); private final Queue<NetworkManager.QueuedPacket> getPacketQueue() { return this.i; } // Paper - OBFHELPER // Akarin
|
||||
private final ReentrantReadWriteUpdateLock j = new ReentrantReadWriteUpdateLock(); // Akarin - use update lock
|
||||
public Channel channel;
|
||||
// Spigot Start // PAIL
|
||||
public SocketAddress l;
|
||||
public java.util.UUID spoofedUUID;
|
||||
public com.mojang.authlib.properties.Property[] spoofedProfile;
|
||||
public volatile boolean preparing = true; // Akarin - add volatile
|
||||
public boolean preparing = true;
|
||||
// Spigot End
|
||||
private PacketListener m;
|
||||
private IChatBaseComponent n;
|
||||
private boolean o;
|
||||
private boolean p;
|
||||
private int q;
|
||||
private int r;
|
||||
private float s;
|
||||
private float t;
|
||||
private int u;
|
||||
private boolean v;
|
||||
// Paper start - NetworkClient implementation
|
||||
public int protocolVersion;
|
||||
public java.net.InetSocketAddress virtualHost;
|
||||
@@ -97,7 +76,6 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
this.h = enumprotocoldirection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception {
|
||||
super.channelActive(channelhandlercontext);
|
||||
this.channel = channelhandlercontext.channel();
|
||||
@@ -120,37 +98,58 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
NetworkManager.g.debug("Enabled auto read");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext channelhandlercontext) throws Exception {
|
||||
this.close(new ChatMessage("disconnect.endOfStream", new Object[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) throws Exception {
|
||||
ChatMessage chatmessage;
|
||||
|
||||
if (throwable instanceof TimeoutException) {
|
||||
chatmessage = new ChatMessage("disconnect.timeout", new Object[0]);
|
||||
public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {
|
||||
if (throwable instanceof SkipEncodeException) {
|
||||
NetworkManager.g.debug("Skipping packet due to errors", throwable.getCause());
|
||||
} else {
|
||||
chatmessage = new ChatMessage("disconnect.genericReason", new Object[] { "Internal Exception: " + throwable});
|
||||
}
|
||||
boolean flag = !this.v;
|
||||
|
||||
NetworkManager.g.debug(chatmessage.toPlainText(), throwable);
|
||||
this.close(chatmessage);
|
||||
this.v = true;
|
||||
if (this.channel.isOpen()) {
|
||||
if (throwable instanceof TimeoutException) {
|
||||
NetworkManager.g.debug("Timeout", throwable);
|
||||
this.close(new ChatMessage("disconnect.timeout", new Object[0]));
|
||||
} else {
|
||||
ChatMessage chatmessage = new ChatMessage("disconnect.genericReason", new Object[] { "Internal Exception: " + throwable});
|
||||
|
||||
if (flag) {
|
||||
NetworkManager.g.debug("Failed to sent packet", throwable);
|
||||
this.sendPacket(new PacketPlayOutKickDisconnect(chatmessage), (future) -> {
|
||||
this.close(chatmessage); // CraftBukkit - decompile error
|
||||
});
|
||||
this.stopReading();
|
||||
} else {
|
||||
NetworkManager.g.debug("Double fault", throwable);
|
||||
this.close(chatmessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot
|
||||
}
|
||||
|
||||
protected void a(ChannelHandlerContext channelhandlercontext, Packet<?> packet) throws Exception {
|
||||
if (this.channel.isOpen()) {
|
||||
try {
|
||||
((Packet) packet).a(this.m); // CraftBukkit - decompile error
|
||||
a(packet, this.m);
|
||||
} catch (CancelledPacketHandleException cancelledpackethandleexception) {
|
||||
;
|
||||
}
|
||||
|
||||
++this.q;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static <T extends PacketListener> void a(Packet<T> packet, PacketListener packetlistener) {
|
||||
packet.a((T) packetlistener); // CraftBukkit - decompile error
|
||||
}
|
||||
|
||||
public void setPacketListener(PacketListener packetlistener) {
|
||||
Validate.notNull(packetlistener, "packetListener", new Object[0]);
|
||||
NetworkManager.g.debug("Set listener of {} to {}", this, packetlistener);
|
||||
@@ -158,14 +157,18 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
}
|
||||
|
||||
public void sendPacket(Packet<?> packet) {
|
||||
if (this.isConnected() && this.trySendQueue() && !(packet instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) packet).isReady())) { // Paper - Async-Anti-Xray - Add chunk packets which are not ready or all packets if the queue contains chunk packets which are not ready to the queue and send the packets later in the right order
|
||||
//this.m(); // Paper - Async-Anti-Xray - Move to if statement (this.trySendQueue())
|
||||
this.a(packet, (GenericFutureListener[]) null);
|
||||
this.sendPacket(packet, (GenericFutureListener) null);
|
||||
}
|
||||
|
||||
public void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
||||
if (this.isConnected() && this.sendPacketQueue() && !(packet instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) packet).isReady())) { // Paper - Async-Anti-Xray - Add chunk packets which are not ready or all packets if the packet queue contains chunk packets which are not ready to the packet queue and send the packets later in the right order
|
||||
//this.o(); // Paper - Async-Anti-Xray - Move to if statement (this.sendPacketQueue())
|
||||
this.b(packet, genericfuturelistener);
|
||||
} else {
|
||||
this.j.writeLock().lock();
|
||||
|
||||
try {
|
||||
this.i.add(new NetworkManager.QueuedPacket(packet, new GenericFutureListener[0]));
|
||||
this.i.add(new NetworkManager.QueuedPacket(packet, genericfuturelistener));
|
||||
} finally {
|
||||
this.j.writeLock().unlock();
|
||||
}
|
||||
@@ -173,27 +176,12 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
}
|
||||
|
||||
public void sendPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> genericfuturelistener, GenericFutureListener<? extends Future<? super Void>>... agenericfuturelistener) {
|
||||
if (this.isConnected() && this.trySendQueue() && !(packet instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) packet).isReady())) { // Paper - Async-Anti-Xray - Add chunk packets which are not ready or all packets if the queue contains chunk packets which are not ready to the queue and send the packets later in the right order
|
||||
//this.m(); // Paper - Async-Anti-Xray - Move to if statement (this.trySendQueue())
|
||||
this.a(packet, ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener));
|
||||
} else {
|
||||
this.j.writeLock().lock();
|
||||
|
||||
try {
|
||||
this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener)));
|
||||
} finally {
|
||||
this.j.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void dispatchPacket(final Packet<?> packet, @Nullable final GenericFutureListener<? extends Future<? super Void>>[] genericFutureListeners) { this.a(packet, genericFutureListeners); } // Paper - Anti-Xray - OBFHELPER
|
||||
private void a(final Packet<?> packet, @Nullable final GenericFutureListener<? extends Future<? super Void>>[] agenericfuturelistener) {
|
||||
final EnumProtocol enumprotocol = EnumProtocol.a(packet);
|
||||
final EnumProtocol enumprotocol1 = this.channel.attr(NetworkManager.c).get();
|
||||
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
|
||||
private void b(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
||||
EnumProtocol enumprotocol = EnumProtocol.a(packet);
|
||||
EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get();
|
||||
|
||||
++this.r;
|
||||
if (enumprotocol1 != enumprotocol) {
|
||||
NetworkManager.g.debug("Disabled auto read");
|
||||
this.channel.config().setAutoRead(false);
|
||||
@@ -206,35 +194,32 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
|
||||
|
||||
if (agenericfuturelistener != null) {
|
||||
channelfuture.addListeners(agenericfuturelistener);
|
||||
if (genericfuturelistener != null) {
|
||||
channelfuture.addListener(genericfuturelistener);
|
||||
}
|
||||
|
||||
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
} else {
|
||||
this.channel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (enumprotocol != enumprotocol1) {
|
||||
NetworkManager.this.setProtocol(enumprotocol);
|
||||
}
|
||||
|
||||
ChannelFuture channelfuture = NetworkManager.this.channel.writeAndFlush(packet);
|
||||
|
||||
if (agenericfuturelistener != null) {
|
||||
channelfuture.addListeners(agenericfuturelistener);
|
||||
}
|
||||
|
||||
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
this.channel.eventLoop().execute(() -> {
|
||||
if (enumprotocol != enumprotocol1) {
|
||||
this.setProtocol(enumprotocol);
|
||||
}
|
||||
|
||||
ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
|
||||
|
||||
if (genericfuturelistener != null) {
|
||||
channelfuture.addListener(genericfuturelistener);
|
||||
}
|
||||
|
||||
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Paper start - Async-Anti-Xray - Stop dispatching further packets and return false if the peeked packet is a chunk packet which is not ready
|
||||
private boolean trySendQueue() { return this.m(); } // OBFHELPER
|
||||
private boolean m() { // void -> boolean
|
||||
private boolean sendPacketQueue() { return this.o(); } // OBFHELPER // void -> boolean
|
||||
private boolean o() { // void -> boolean
|
||||
if (this.channel != null && this.channel.isOpen()) {
|
||||
if (this.i.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;
|
||||
@@ -244,14 +229,14 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
try {
|
||||
while (!this.i.isEmpty()) {
|
||||
NetworkManager.QueuedPacket networkmanager_queuedpacket = this.getPacketQueue().peek(); // poll -> peek
|
||||
NetworkManager.QueuedPacket networkmanager_queuedpacket = (NetworkManager.QueuedPacket) this.getPacketQueue().peek(); // poll -> peek
|
||||
|
||||
if (networkmanager_queuedpacket != null) { // Fix NPE (Spigot bug caused by handleDisconnection())
|
||||
if (networkmanager_queuedpacket.getPacket() instanceof PacketPlayOutMapChunk && !((PacketPlayOutMapChunk) networkmanager_queuedpacket.getPacket()).isReady()) { // Check if the peeked packet is a chunk packet which is not ready
|
||||
return false; // Return false if the peeked packet is a chunk packet which is not ready
|
||||
} else {
|
||||
this.getPacketQueue().poll(); // poll here
|
||||
this.dispatchPacket(networkmanager_queuedpacket.getPacket(), networkmanager_queuedpacket.getGenericFutureListeners()); // dispatch the packet
|
||||
this.dispatchPacket(networkmanager_queuedpacket.getPacket(), networkmanager_queuedpacket.getGenericFutureListener()); // dispatch the packet
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,15 +251,22 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
// Paper end
|
||||
|
||||
public void a() {
|
||||
this.m();
|
||||
this.o();
|
||||
if (this.m instanceof ITickable) {
|
||||
((ITickable) this.m).e();
|
||||
((ITickable) this.m).Y_();
|
||||
}
|
||||
|
||||
if (this.channel != null) {
|
||||
if (enableExplicitFlush) this.channel.eventLoop().execute(() -> this.channel.flush()); // Paper - we don't need to explicit flush here, but allow opt in incase issues are found to a better version
|
||||
}
|
||||
|
||||
if (this.u++ % 20 == 0) {
|
||||
this.t = this.t * 0.75F + (float) this.r * 0.25F;
|
||||
this.s = this.s * 0.75F + (float) this.q * 0.25F;
|
||||
this.r = 0;
|
||||
this.q = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SocketAddress getSocketAddress() {
|
||||
@@ -314,6 +306,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
return this.m;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IChatBaseComponent j() {
|
||||
return this.n;
|
||||
}
|
||||
@@ -364,19 +357,19 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet object) throws Exception { // CraftBukkit - fix decompile error
|
||||
this.a(channelhandlercontext, object);
|
||||
this.a(channelhandlercontext, (Packet) object);
|
||||
}
|
||||
|
||||
public static class QueuedPacket { // Akarin - default -> public
|
||||
public static class QueuedPacket { // Akarin
|
||||
|
||||
private final Packet<?> a; public final Packet<?> getPacket() { return this.a; } // Paper - Anti-Xray - OBFHELPER // Akarin - private -> public
|
||||
private final GenericFutureListener<? extends Future<? super Void>>[] b; public final GenericFutureListener<? extends Future<? super Void>>[] getGenericFutureListeners() { return this.b; } // Paper - Anti-Xray - OBFHELPER // Akarin - private -> public
|
||||
private final Packet<?> a; public final Packet<?> getPacket() { return this.a; } // Paper - OBFHELPER // Akarin
|
||||
@Nullable
|
||||
private final GenericFutureListener<? extends Future<? super Void>> b; public final GenericFutureListener<? extends Future<? super Void>> getGenericFutureListener() { return this.b; } // Paper - OBFHELPER // Akarin
|
||||
|
||||
public QueuedPacket(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>>... agenericfuturelistener) {
|
||||
public QueuedPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
||||
this.a = packet;
|
||||
this.b = agenericfuturelistener;
|
||||
this.b = genericfuturelistener;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
588
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
588
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
@@ -0,0 +1,588 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
// CraftBukkit start
|
||||
import java.util.LinkedList;
|
||||
// CraftBukkit end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add synchronizations (safety issue)
|
||||
*/
|
||||
public class PlayerChunkMap {
|
||||
|
||||
private static final Predicate<EntityPlayer> a = (entityplayer) -> {
|
||||
return entityplayer != null && !entityplayer.isSpectator();
|
||||
};
|
||||
private static final Predicate<EntityPlayer> b = (entityplayer) -> {
|
||||
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.getWorldServer().getGameRules().getBoolean("spectatorsGenerateChunks"));
|
||||
};
|
||||
private final WorldServer world;
|
||||
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
|
||||
private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
|
||||
private final Set<PlayerChunk> f = Sets.newHashSet();
|
||||
private final List<PlayerChunk> g = Lists.newLinkedList();
|
||||
private final List<PlayerChunk> h = Lists.newLinkedList();
|
||||
private final List<PlayerChunk> i = Lists.newCopyOnWriteArrayList(); // Akarin - bad plugin will access this
|
||||
private int j; public int getViewDistance() { return j; } // Paper OBFHELPER
|
||||
private long k;
|
||||
private boolean l = true;
|
||||
private boolean m = true;
|
||||
private boolean wasNotEmpty; // CraftBukkit - add field
|
||||
|
||||
public PlayerChunkMap(WorldServer worldserver) {
|
||||
this.world = worldserver;
|
||||
this.a(worldserver.spigotConfig.viewDistance); // Spigot
|
||||
}
|
||||
|
||||
public WorldServer getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
public Iterator<Chunk> b() {
|
||||
final Iterator iterator = this.i.iterator();
|
||||
|
||||
return new AbstractIterator() {
|
||||
protected Chunk a() {
|
||||
while (true) {
|
||||
if (iterator.hasNext()) {
|
||||
PlayerChunk playerchunk = (PlayerChunk) iterator.next();
|
||||
Chunk chunk = playerchunk.f();
|
||||
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chunk.v()) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
if (!playerchunk.a(128.0D, PlayerChunkMap.a)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return (Chunk) this.endOfData();
|
||||
}
|
||||
}
|
||||
|
||||
protected Object computeNext() {
|
||||
return this.a();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
long i = this.world.getTime();
|
||||
int j;
|
||||
PlayerChunk playerchunk;
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
|
||||
if (i - this.k > 8000L) {
|
||||
try (Timing ignored = world.timings.doChunkMapUpdate.startTiming()) { // Paper
|
||||
this.k = i;
|
||||
|
||||
for (j = 0; j < this.i.size(); ++j) {
|
||||
playerchunk = (PlayerChunk) this.i.get(j);
|
||||
playerchunk.d();
|
||||
playerchunk.c();
|
||||
}
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (!this.f.isEmpty()) {
|
||||
try (Timing ignored = world.timings.doChunkMapToUpdate.startTiming()) { // Paper
|
||||
Iterator iterator = this.f.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
playerchunk = (PlayerChunk) iterator.next();
|
||||
playerchunk.d();
|
||||
}
|
||||
|
||||
this.f.clear();
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (this.l && i % 4L == 0L) {
|
||||
this.l = false;
|
||||
try (Timing ignored = world.timings.doChunkMapSortMissing.startTiming()) { // Paper
|
||||
// CraftBukkit start
|
||||
Collections.sort(this.h, (playerchunkx, playerchunk1x) -> {
|
||||
return ComparisonChain.start().compare(playerchunkx.g(), playerchunk1x.g()).result();
|
||||
});
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (this.m && i % 4L == 2L) {
|
||||
this.m = false;
|
||||
try (Timing ignored = world.timings.doChunkMapSortSendToPlayers.startTiming()) { // Paper
|
||||
Collections.sort(this.g, (playerchunkx, playerchunk1x) -> {
|
||||
return ComparisonChain.start().compare(playerchunkx.g(), playerchunk1x.g()).result();
|
||||
});
|
||||
} // Paper timing
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
if (!this.h.isEmpty()) {
|
||||
try (Timing ignored = world.timings.doChunkMapPlayersNeedingChunks.startTiming()) { // Paper
|
||||
// Spigot start
|
||||
org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant;
|
||||
activityAccountant.startActivity(0.5);
|
||||
int chunkGensAllowed = world.paperConfig.maxChunkGensPerTick; // Paper
|
||||
// Spigot end
|
||||
|
||||
Iterator iterator1 = this.h.iterator();
|
||||
|
||||
while (iterator1.hasNext()) {
|
||||
PlayerChunk playerchunk1 = (PlayerChunk) iterator1.next();
|
||||
|
||||
if (playerchunk1.f() == null) {
|
||||
boolean flag = playerchunk1.a(PlayerChunkMap.b);
|
||||
// Paper start
|
||||
if (flag && !playerchunk1.chunkExists && chunkGensAllowed-- <= 0) {
|
||||
continue;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
if (playerchunk1.a(flag)) {
|
||||
iterator1.remove();
|
||||
if (playerchunk1.b()) {
|
||||
this.g.remove(playerchunk1);
|
||||
}
|
||||
|
||||
if (activityAccountant.activityTimeIsExhausted()) { // Spigot
|
||||
break;
|
||||
}
|
||||
}
|
||||
// CraftBukkit start - SPIGOT-2891: remove once chunk has been provided
|
||||
} else {
|
||||
iterator1.remove();
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
activityAccountant.endActivity(); // Spigot
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (!this.g.isEmpty()) {
|
||||
j = world.paperConfig.maxChunkSendsPerTick; // Paper
|
||||
try (Timing ignored = world.timings.doChunkMapPendingSendToPlayers.startTiming()) { // Paper
|
||||
Iterator iterator2 = this.g.iterator();
|
||||
|
||||
while (iterator2.hasNext()) {
|
||||
PlayerChunk playerchunk2 = (PlayerChunk) iterator2.next();
|
||||
|
||||
if (playerchunk2.b()) {
|
||||
iterator2.remove();
|
||||
--j;
|
||||
if (j < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (this.managedPlayers.isEmpty()) {
|
||||
try (Timing ignored = world.timings.doChunkMapUnloadChunks.startTiming()) { // Paper
|
||||
WorldProvider worldprovider = this.world.worldProvider;
|
||||
|
||||
if (!worldprovider.p() && !this.world.savingDisabled) { // Paper - respect saving disabled setting
|
||||
this.world.getChunkProviderServer().b();
|
||||
}
|
||||
} // Paper timing
|
||||
}
|
||||
} // Akarin
|
||||
|
||||
}
|
||||
|
||||
public boolean a(int i, int j) {
|
||||
long k = d(i, j);
|
||||
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
return this.e.get(k) != null;
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PlayerChunk getChunk(int i, int j) {
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
return (PlayerChunk) this.e.get(d(i, j));
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
private PlayerChunk c(int i, int j) {
|
||||
long k = d(i, j);
|
||||
PlayerChunk playerchunk = (PlayerChunk) this.e.get(k);
|
||||
|
||||
if (playerchunk == null) {
|
||||
playerchunk = new PlayerChunk(this, i, j);
|
||||
this.e.put(k, playerchunk);
|
||||
this.i.add(playerchunk);
|
||||
if (playerchunk.f() == null) {
|
||||
this.h.add(playerchunk);
|
||||
}
|
||||
|
||||
if (!playerchunk.b()) {
|
||||
this.g.add(playerchunk);
|
||||
}
|
||||
}
|
||||
|
||||
return playerchunk;
|
||||
}
|
||||
|
||||
// CraftBukkit start - add method
|
||||
public final boolean isChunkInUse(int x, int z) {
|
||||
PlayerChunk pi = getChunk(x, z);
|
||||
if (pi != null) {
|
||||
return (pi.c.size() > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
public void flagDirty(BlockPosition blockposition) {
|
||||
int i = blockposition.getX() >> 4;
|
||||
int j = blockposition.getZ() >> 4;
|
||||
PlayerChunk playerchunk = this.getChunk(i, j);
|
||||
|
||||
if (playerchunk != null) {
|
||||
playerchunk.a(blockposition.getX() & 15, blockposition.getY(), blockposition.getZ() & 15);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void addPlayer(EntityPlayer entityplayer) {
|
||||
int i = (int) entityplayer.locX >> 4;
|
||||
int j = (int) entityplayer.locZ >> 4;
|
||||
|
||||
entityplayer.d = entityplayer.locX;
|
||||
entityplayer.e = entityplayer.locZ;
|
||||
|
||||
|
||||
// CraftBukkit start - Load nearby chunks first
|
||||
List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
|
||||
|
||||
// Paper start - Player view distance API
|
||||
int viewDistance = entityplayer.getViewDistance();
|
||||
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
|
||||
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
|
||||
// Paper end
|
||||
chunkList.add(new ChunkCoordIntPair(k, l));
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(chunkList, new ChunkCoordComparator(entityplayer));
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
for (ChunkCoordIntPair pair : chunkList) {
|
||||
this.c(pair.x, pair.z).a(entityplayer);
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
this.managedPlayers.add(entityplayer);
|
||||
} // Akarin
|
||||
this.e();
|
||||
}
|
||||
|
||||
public void removePlayer(EntityPlayer entityplayer) {
|
||||
int i = (int) entityplayer.d >> 4;
|
||||
int j = (int) entityplayer.e >> 4;
|
||||
|
||||
// Paper start - Player view distance API
|
||||
int viewDistance = entityplayer.getViewDistance();
|
||||
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
|
||||
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
|
||||
// Paper end
|
||||
PlayerChunk playerchunk = this.getChunk(k, l);
|
||||
|
||||
if (playerchunk != null) {
|
||||
playerchunk.b(entityplayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
this.managedPlayers.remove(entityplayer);
|
||||
} // Akarin
|
||||
this.e();
|
||||
}
|
||||
|
||||
private boolean a(int i, int j, int k, int l, int i1) {
|
||||
int j1 = i - k;
|
||||
int k1 = j - l;
|
||||
|
||||
return j1 >= -i1 && j1 <= i1 ? k1 >= -i1 && k1 <= i1 : false;
|
||||
}
|
||||
|
||||
public void movePlayer(EntityPlayer entityplayer) {
|
||||
int i = (int) entityplayer.locX >> 4;
|
||||
int j = (int) entityplayer.locZ >> 4;
|
||||
double d0 = entityplayer.d - entityplayer.locX;
|
||||
double d1 = entityplayer.e - entityplayer.locZ;
|
||||
double d2 = d0 * d0 + d1 * d1;
|
||||
|
||||
if (d2 >= 64.0D) {
|
||||
int k = (int) entityplayer.d >> 4;
|
||||
int l = (int) entityplayer.e >> 4;
|
||||
int i1 = entityplayer.getViewDistance(); // Paper - Player view distance API
|
||||
|
||||
int j1 = i - k;
|
||||
int k1 = j - l;
|
||||
|
||||
List<ChunkCoordIntPair> chunksToLoad = new LinkedList<ChunkCoordIntPair>(); // CraftBukkit
|
||||
|
||||
if (j1 != 0 || k1 != 0) {
|
||||
for (int l1 = i - i1; l1 <= i + i1; ++l1) {
|
||||
for (int i2 = j - i1; i2 <= j + i1; ++i2) {
|
||||
if (!this.a(l1, i2, k, l, i1)) {
|
||||
// this.c(l1, i2).a(entityplayer);
|
||||
chunksToLoad.add(new ChunkCoordIntPair(l1, i2)); // CraftBukkit
|
||||
}
|
||||
|
||||
if (!this.a(l1 - j1, i2 - k1, i, j, i1)) {
|
||||
PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
|
||||
|
||||
if (playerchunk != null) {
|
||||
playerchunk.b(entityplayer);
|
||||
}
|
||||
} else { // Paper start
|
||||
PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
|
||||
if (playerchunk != null) {
|
||||
playerchunk.checkHighPriority(entityplayer); // Paper
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
}
|
||||
|
||||
entityplayer.d = entityplayer.locX;
|
||||
entityplayer.e = entityplayer.locZ;
|
||||
this.e();
|
||||
|
||||
// CraftBukkit start - send nearest chunks first
|
||||
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
for (ChunkCoordIntPair pair : chunksToLoad) {
|
||||
// Paper start
|
||||
PlayerChunk c = this.c(pair.x, pair.z);
|
||||
c.checkHighPriority(entityplayer);
|
||||
c.a(entityplayer);
|
||||
// Paper end
|
||||
}
|
||||
} // Akarin
|
||||
// CraftBukkit end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean a(EntityPlayer entityplayer, int i, int j) {
|
||||
PlayerChunk playerchunk = this.getChunk(i, j);
|
||||
|
||||
return playerchunk != null && playerchunk.d(entityplayer) && playerchunk.e();
|
||||
}
|
||||
|
||||
public final void setViewDistanceForAll(int viewDistance) { this.a(viewDistance); } // Paper - OBFHELPER
|
||||
// Paper start - Separate into two methods
|
||||
public void a(int i) {
|
||||
i = MathHelper.clamp(i, 3, 32);
|
||||
if (i != this.j) {
|
||||
int j = i - this.j;
|
||||
ArrayList arraylist; // Akarin
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
arraylist = Lists.newArrayList(this.managedPlayers);
|
||||
} // Akarin
|
||||
Iterator iterator = arraylist.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
|
||||
this.setViewDistance(entityplayer, i, false); // Paper - Split, don't mark sort pending, we'll handle it after
|
||||
}
|
||||
|
||||
this.j = i;
|
||||
this.e();
|
||||
}
|
||||
}
|
||||
|
||||
public void setViewDistance(EntityPlayer entityplayer, int i) {
|
||||
this.setViewDistance(entityplayer, i, true); // Mark sort pending by default so we don't have to remember to do so all the time
|
||||
}
|
||||
|
||||
// Copied from above with minor changes
|
||||
public void setViewDistance(EntityPlayer entityplayer, int i, boolean markSort) {
|
||||
i = MathHelper.clamp(i, 3, 32);
|
||||
int oldViewDistance = entityplayer.getViewDistance();
|
||||
if (i != oldViewDistance) {
|
||||
int j = i - oldViewDistance;
|
||||
|
||||
int k = (int) entityplayer.locX >> 4;
|
||||
int l = (int) entityplayer.locZ >> 4;
|
||||
int i1;
|
||||
int j1;
|
||||
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
if (j > 0) {
|
||||
for (i1 = k - i; i1 <= k + i; ++i1) {
|
||||
for (j1 = l - i; j1 <= l + i; ++j1) {
|
||||
PlayerChunk playerchunk = this.c(i1, j1);
|
||||
|
||||
if (!playerchunk.d(entityplayer)) {
|
||||
playerchunk.a(entityplayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) {
|
||||
for (j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) {
|
||||
if (!this.a(i1, j1, k, l, i)) {
|
||||
this.c(i1, j1).b(entityplayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (markSort) {
|
||||
this.e();
|
||||
}
|
||||
}
|
||||
} // Akarin
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
|
||||
private void e() {
|
||||
this.l = true;
|
||||
this.m = true;
|
||||
}
|
||||
|
||||
public static int getFurthestViewableBlock(int i) {
|
||||
return i * 16 - 16;
|
||||
}
|
||||
|
||||
private static long d(int i, int j) {
|
||||
return (long) i + 2147483647L | (long) j + 2147483647L << 32;
|
||||
}
|
||||
|
||||
public void a(PlayerChunk playerchunk) {
|
||||
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Add"); // Paper
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
this.f.add(playerchunk);
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public void b(PlayerChunk playerchunk) {
|
||||
synchronized (((IMixinWorldServer) world).trackLock()) { // Akarin
|
||||
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Remove"); // Paper
|
||||
ChunkCoordIntPair chunkcoordintpair = playerchunk.a();
|
||||
long i = d(chunkcoordintpair.x, chunkcoordintpair.z);
|
||||
|
||||
playerchunk.c();
|
||||
this.e.remove(i);
|
||||
this.i.remove(playerchunk);
|
||||
this.f.remove(playerchunk);
|
||||
this.g.remove(playerchunk);
|
||||
this.h.remove(playerchunk);
|
||||
Chunk chunk = playerchunk.f();
|
||||
|
||||
if (chunk != null) {
|
||||
// Paper start - delay chunk unloads
|
||||
if (world.paperConfig.delayChunkUnloadsBy <= 0) {
|
||||
this.getWorld().getChunkProviderServer().unload(chunk);
|
||||
} else {
|
||||
chunk.scheduledForUnload = System.currentTimeMillis();
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
} // Akarin
|
||||
|
||||
}
|
||||
|
||||
// CraftBukkit start - Sorter to load nearby chunks first
|
||||
private static class ChunkCoordComparator implements java.util.Comparator<ChunkCoordIntPair> {
|
||||
private int x;
|
||||
private int z;
|
||||
|
||||
public ChunkCoordComparator (EntityPlayer entityplayer) {
|
||||
x = (int) entityplayer.locX >> 4;
|
||||
z = (int) entityplayer.locZ >> 4;
|
||||
}
|
||||
|
||||
public int compare(ChunkCoordIntPair a, ChunkCoordIntPair b) {
|
||||
if (a.equals(b)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Subtract current position to set center point
|
||||
int ax = a.x - this.x;
|
||||
int az = a.z - this.z;
|
||||
int bx = b.x - this.x;
|
||||
int bz = b.z - this.z;
|
||||
|
||||
int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz));
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (ax < 0) {
|
||||
if (bx < 0) {
|
||||
return bz - az;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (bx < 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return az - bz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
// Paper start - Player view distance API
|
||||
public void updateViewDistance(EntityPlayer player, int distanceIn) {
|
||||
final int oldViewDistance = player.getViewDistance();
|
||||
|
||||
// This represents the view distance that we will set on the player
|
||||
// It can exist as a negative value
|
||||
int playerViewDistance = MathHelper.clamp(distanceIn, 3, 32);
|
||||
|
||||
// This value is the one we actually use to update the chunk map
|
||||
// We don't ever want this to be a negative
|
||||
int toSet = playerViewDistance;
|
||||
|
||||
if (distanceIn < 0) {
|
||||
playerViewDistance = -1;
|
||||
toSet = world.getPlayerChunkMap().getViewDistance();
|
||||
}
|
||||
|
||||
if (toSet != oldViewDistance) {
|
||||
// Order matters
|
||||
this.setViewDistance(player, toSet);
|
||||
player.setViewDistance(playerViewDistance);
|
||||
|
||||
//Force update entity trackers
|
||||
this.getWorld().getTracker().updatePlayer(player);
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
274
sources/src/main/java/net/minecraft/server/TileEntitySkull.java
Normal file
274
sources/src/main/java/net/minecraft/server/TileEntitySkull.java
Normal file
@@ -0,0 +1,274 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// Spigot start
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.ProfileLookupCallback;
|
||||
import java.util.concurrent.Callable;
|
||||
// Spigot end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Guava -> Caffeine (performance)
|
||||
*/
|
||||
public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Paper - remove tickable
|
||||
|
||||
private GameProfile a;
|
||||
private int e;
|
||||
private boolean f;
|
||||
public boolean drop = true;
|
||||
private static UserCache h;
|
||||
private static MinecraftSessionService i;
|
||||
// Spigot start
|
||||
public static final ExecutorService executor = Executors.newFixedThreadPool(3,
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("Head Conversion Thread - %1$d")
|
||||
.build()
|
||||
);
|
||||
public static final com.github.benmanes.caffeine.cache.LoadingCache<String, GameProfile> skinCache = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() // Akarin - caffeine
|
||||
.maximumSize( 5000 )
|
||||
.expireAfterAccess( 60, TimeUnit.MINUTES )
|
||||
.build( new com.github.benmanes.caffeine.cache.CacheLoader<String, GameProfile>() // Akarin - caffeine
|
||||
{
|
||||
@Override
|
||||
public GameProfile load(String key) // Akarin - remove exception
|
||||
{
|
||||
final GameProfile[] profiles = new GameProfile[1];
|
||||
ProfileLookupCallback gameProfileLookup = new ProfileLookupCallback() {
|
||||
|
||||
@Override
|
||||
public void onProfileLookupSucceeded(GameProfile gp) {
|
||||
profiles[0] = gp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProfileLookupFailed(GameProfile gp, Exception excptn) {
|
||||
profiles[0] = gp;
|
||||
}
|
||||
};
|
||||
|
||||
MinecraftServer.getServer().getGameProfileRepository().findProfilesByNames(new String[] { key }, Agent.MINECRAFT, gameProfileLookup);
|
||||
|
||||
GameProfile profile = profiles[ 0 ];
|
||||
if (profile == null) {
|
||||
UUID uuid = EntityHuman.a(new GameProfile(null, key));
|
||||
profile = new GameProfile(uuid, key);
|
||||
|
||||
gameProfileLookup.onProfileLookupSucceeded(profile);
|
||||
} else
|
||||
{
|
||||
|
||||
Property property = Iterables.getFirst( profile.getProperties().get( "textures" ), null );
|
||||
|
||||
if ( property == null )
|
||||
{
|
||||
profile = TileEntitySkull.i.fillProfileProperties( profile, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return profile;
|
||||
}
|
||||
} );
|
||||
// Spigot end
|
||||
|
||||
public TileEntitySkull() {
|
||||
super(TileEntityTypes.SKULL);
|
||||
}
|
||||
|
||||
public static void a(UserCache usercache) {
|
||||
TileEntitySkull.h = usercache;
|
||||
}
|
||||
|
||||
public static void a(MinecraftSessionService minecraftsessionservice) {
|
||||
TileEntitySkull.i = minecraftsessionservice;
|
||||
}
|
||||
|
||||
public NBTTagCompound save(NBTTagCompound nbttagcompound) {
|
||||
super.save(nbttagcompound);
|
||||
if (this.a != null) {
|
||||
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
|
||||
|
||||
GameProfileSerializer.serialize(nbttagcompound1, this.a);
|
||||
nbttagcompound.set("Owner", nbttagcompound1);
|
||||
}
|
||||
|
||||
return nbttagcompound;
|
||||
}
|
||||
|
||||
public void load(NBTTagCompound nbttagcompound) {
|
||||
super.load(nbttagcompound);
|
||||
if (nbttagcompound.hasKeyOfType("Owner", 10)) {
|
||||
this.setGameProfile(GameProfileSerializer.deserialize(nbttagcompound.getCompound("Owner")));
|
||||
} else if (nbttagcompound.hasKeyOfType("ExtraType", 8)) {
|
||||
String s = nbttagcompound.getString("ExtraType");
|
||||
|
||||
if (!UtilColor.b(s)) {
|
||||
this.setGameProfile(new GameProfile((UUID) null, s));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Y_() {
|
||||
Block block = this.getBlock().getBlock();
|
||||
|
||||
if (block == Blocks.DRAGON_HEAD || block == Blocks.DRAGON_WALL_HEAD) {
|
||||
if (this.world.isBlockIndirectlyPowered(this.position)) {
|
||||
this.f = true;
|
||||
++this.e;
|
||||
} else {
|
||||
this.f = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GameProfile getGameProfile() {
|
||||
return this.a;
|
||||
}
|
||||
|
||||
// Paper start
|
||||
static NBTTagCompound sanitizeTileEntityUUID(NBTTagCompound cmp) {
|
||||
NBTTagCompound owner = cmp.getCompound("Owner");
|
||||
if (!owner.isEmpty()) {
|
||||
sanitizeUUID(owner);
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
static void sanitizeUUID(NBTTagCompound owner) {
|
||||
NBTTagCompound properties = owner.getCompound("Properties");
|
||||
NBTTagList list = null;
|
||||
if (!properties.isEmpty()) {
|
||||
list = properties.getList("textures", 10);
|
||||
}
|
||||
|
||||
if (list != null && !list.isEmpty()) {
|
||||
String textures = ((NBTTagCompound)list.get(0)).getString("Value");
|
||||
if (textures != null && textures.length() > 3) {
|
||||
String uuid = UUID.nameUUIDFromBytes(textures.getBytes()).toString();
|
||||
owner.setString("Id", uuid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
owner.setString("Id", UUID.randomUUID().toString());
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@Nullable
|
||||
public PacketPlayOutTileEntityData getUpdatePacket() {
|
||||
return new PacketPlayOutTileEntityData(this.position, 4, sanitizeTileEntityUUID(this.aa_())); // Paper
|
||||
}
|
||||
|
||||
public NBTTagCompound aa_() {
|
||||
return this.save(new NBTTagCompound());
|
||||
}
|
||||
|
||||
public void setGameProfile(@Nullable GameProfile gameprofile) {
|
||||
this.a = gameprofile;
|
||||
this.f();
|
||||
}
|
||||
|
||||
private void f() {
|
||||
// Spigot start
|
||||
GameProfile profile = this.getGameProfile();
|
||||
b(profile, new Predicate<GameProfile>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(GameProfile input) {
|
||||
a = input;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
}, false);
|
||||
// Spigot end
|
||||
}
|
||||
|
||||
// Spigot start - Support async lookups
|
||||
public static Future<GameProfile> b(final GameProfile gameprofile, final Predicate<GameProfile> callback, boolean sync) {
|
||||
if (gameprofile != null && !UtilColor.b(gameprofile.getName())) {
|
||||
if (gameprofile.isComplete() && gameprofile.getProperties().containsKey("textures")) {
|
||||
callback.apply(gameprofile);
|
||||
} else if (MinecraftServer.getServer() == null) {
|
||||
callback.apply(gameprofile);
|
||||
} else {
|
||||
GameProfile profile = skinCache.getIfPresent(gameprofile.getName().toLowerCase(java.util.Locale.ROOT));
|
||||
if (profile != null && Iterables.getFirst(profile.getProperties().get("textures"), (Object) null) != null) {
|
||||
callback.apply(profile);
|
||||
|
||||
return Futures.immediateFuture(profile);
|
||||
} else {
|
||||
Callable<GameProfile> callable = new Callable<GameProfile>() {
|
||||
@Override
|
||||
public GameProfile call() {
|
||||
final GameProfile profile = skinCache.get(gameprofile.getName().toLowerCase(java.util.Locale.ROOT)); // Akarin - caffeine
|
||||
MinecraftServer.getServer().processQueue.add(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (profile == null) {
|
||||
callback.apply(gameprofile);
|
||||
} else {
|
||||
callback.apply(profile);
|
||||
}
|
||||
}
|
||||
});
|
||||
return profile;
|
||||
}
|
||||
};
|
||||
if (sync) {
|
||||
try {
|
||||
return Futures.immediateFuture(callable.call());
|
||||
} catch (Exception ex) {
|
||||
com.google.common.base.Throwables.throwIfUnchecked(ex);
|
||||
throw new RuntimeException(ex); // Not possible
|
||||
}
|
||||
} else {
|
||||
return executor.submit(callable);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback.apply(gameprofile);
|
||||
}
|
||||
|
||||
return Futures.immediateFuture(gameprofile);
|
||||
}
|
||||
// Spigot end
|
||||
|
||||
// CraftBukkit start
|
||||
public static void a(IBlockAccess iblockaccess, BlockPosition blockposition) {
|
||||
setShouldDrop(iblockaccess, blockposition, false);
|
||||
}
|
||||
|
||||
public static void setShouldDrop(IBlockAccess iblockaccess, BlockPosition blockposition, boolean flag) {
|
||||
// CraftBukkit end
|
||||
TileEntity tileentity = iblockaccess.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntitySkull) {
|
||||
TileEntitySkull tileentityskull = (TileEntitySkull) tileentity;
|
||||
|
||||
tileentityskull.drop = flag; // CraftBukkit
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean shouldDrop() {
|
||||
return this.drop;
|
||||
}
|
||||
}
|
||||
@@ -1,775 +0,0 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import javax.annotation.Nullable;
|
||||
// CraftBukkit start
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.weather.ThunderChangeEvent;
|
||||
import org.bukkit.event.weather.WeatherChangeEvent;
|
||||
// CraftBukkit end
|
||||
|
||||
/**
|
||||
* <b>Akarin Changes Note</b><br>
|
||||
* <br>
|
||||
* 1) Add volatile to fields<br>
|
||||
* @author cakoyo
|
||||
*/
|
||||
public class WorldData {
|
||||
|
||||
private String b;
|
||||
private int c;
|
||||
private boolean d;
|
||||
public static final EnumDifficulty a = EnumDifficulty.NORMAL;
|
||||
private long e;
|
||||
private WorldType f;
|
||||
private String g;
|
||||
private int h;
|
||||
private int i;
|
||||
private int j;
|
||||
private volatile long k; // Akarin - volatile - PAIL: time
|
||||
private volatile long l; // Akarin - volatile - PAIL: dayTime
|
||||
private long m;
|
||||
private long n;
|
||||
private NBTTagCompound o;
|
||||
private int p;
|
||||
private String levelName;
|
||||
private int r;
|
||||
private int s;
|
||||
private boolean t;
|
||||
private int u;
|
||||
private boolean v;
|
||||
private int w;
|
||||
private EnumGamemode x;
|
||||
private boolean y;
|
||||
private boolean z;
|
||||
private boolean A;
|
||||
private boolean B;
|
||||
private EnumDifficulty C;
|
||||
private boolean D;
|
||||
private double E;
|
||||
private double F;
|
||||
private double G;
|
||||
private long H;
|
||||
private double I;
|
||||
private double J;
|
||||
private double K;
|
||||
private int L;
|
||||
private int M;
|
||||
private final Map<DimensionManager, NBTTagCompound> N;
|
||||
private GameRules O;
|
||||
public WorldServer world; // CraftBukkit
|
||||
|
||||
protected WorldData() {
|
||||
this.f = WorldType.NORMAL;
|
||||
this.g = "";
|
||||
this.G = 6.0E7D;
|
||||
this.J = 5.0D;
|
||||
this.K = 0.2D;
|
||||
this.L = 5;
|
||||
this.M = 15;
|
||||
this.N = Maps.newEnumMap(DimensionManager.class);
|
||||
this.O = new GameRules();
|
||||
}
|
||||
|
||||
public static void a(DataConverterManager dataconvertermanager) {
|
||||
dataconvertermanager.a(DataConverterTypes.LEVEL, new DataInspector() {
|
||||
@Override
|
||||
public NBTTagCompound a(DataConverter dataconverter, NBTTagCompound nbttagcompound, int i) {
|
||||
if (nbttagcompound.hasKeyOfType("Player", 10)) {
|
||||
nbttagcompound.set("Player", dataconverter.a(DataConverterTypes.PLAYER, nbttagcompound.getCompound("Player"), i));
|
||||
}
|
||||
|
||||
return nbttagcompound;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public WorldData(NBTTagCompound nbttagcompound) {
|
||||
this.f = WorldType.NORMAL;
|
||||
this.g = "";
|
||||
this.G = 6.0E7D;
|
||||
this.J = 5.0D;
|
||||
this.K = 0.2D;
|
||||
this.L = 5;
|
||||
this.M = 15;
|
||||
this.N = Maps.newEnumMap(DimensionManager.class);
|
||||
this.O = new GameRules();
|
||||
NBTTagCompound nbttagcompound1;
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("Version", 10)) {
|
||||
nbttagcompound1 = nbttagcompound.getCompound("Version");
|
||||
this.b = nbttagcompound1.getString("Name");
|
||||
this.c = nbttagcompound1.getInt("Id");
|
||||
this.d = nbttagcompound1.getBoolean("Snapshot");
|
||||
}
|
||||
|
||||
this.e = nbttagcompound.getLong("RandomSeed");
|
||||
if (nbttagcompound.hasKeyOfType("generatorName", 8)) {
|
||||
String s = nbttagcompound.getString("generatorName");
|
||||
|
||||
this.f = WorldType.getType(s);
|
||||
if (this.f == null) {
|
||||
this.f = WorldType.NORMAL;
|
||||
} else if (this.f.f()) {
|
||||
int i = 0;
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("generatorVersion", 99)) {
|
||||
i = nbttagcompound.getInt("generatorVersion");
|
||||
}
|
||||
|
||||
this.f = this.f.a(i);
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("generatorOptions", 8)) {
|
||||
this.g = nbttagcompound.getString("generatorOptions");
|
||||
}
|
||||
}
|
||||
|
||||
this.x = EnumGamemode.getById(nbttagcompound.getInt("GameType"));
|
||||
if (nbttagcompound.hasKeyOfType("MapFeatures", 99)) {
|
||||
this.y = nbttagcompound.getBoolean("MapFeatures");
|
||||
} else {
|
||||
this.y = true;
|
||||
}
|
||||
|
||||
this.h = nbttagcompound.getInt("SpawnX");
|
||||
this.i = nbttagcompound.getInt("SpawnY");
|
||||
this.j = nbttagcompound.getInt("SpawnZ");
|
||||
this.k = nbttagcompound.getLong("Time");
|
||||
if (nbttagcompound.hasKeyOfType("DayTime", 99)) {
|
||||
this.l = nbttagcompound.getLong("DayTime");
|
||||
} else {
|
||||
this.l = this.k;
|
||||
}
|
||||
|
||||
this.m = nbttagcompound.getLong("LastPlayed");
|
||||
this.n = nbttagcompound.getLong("SizeOnDisk");
|
||||
this.levelName = nbttagcompound.getString("LevelName");
|
||||
this.r = nbttagcompound.getInt("version");
|
||||
this.s = nbttagcompound.getInt("clearWeatherTime");
|
||||
this.u = nbttagcompound.getInt("rainTime");
|
||||
this.t = nbttagcompound.getBoolean("raining");
|
||||
this.w = nbttagcompound.getInt("thunderTime");
|
||||
this.v = nbttagcompound.getBoolean("thundering");
|
||||
this.z = nbttagcompound.getBoolean("hardcore");
|
||||
if (nbttagcompound.hasKeyOfType("initialized", 99)) {
|
||||
this.B = nbttagcompound.getBoolean("initialized");
|
||||
} else {
|
||||
this.B = true;
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("allowCommands", 99)) {
|
||||
this.A = nbttagcompound.getBoolean("allowCommands");
|
||||
} else {
|
||||
this.A = this.x == EnumGamemode.CREATIVE;
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("Player", 10)) {
|
||||
this.o = nbttagcompound.getCompound("Player");
|
||||
this.p = this.o.getInt("Dimension");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("GameRules", 10)) {
|
||||
this.O.a(nbttagcompound.getCompound("GameRules"));
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("Difficulty", 99)) {
|
||||
this.C = EnumDifficulty.getById(nbttagcompound.getByte("Difficulty"));
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("DifficultyLocked", 1)) {
|
||||
this.D = nbttagcompound.getBoolean("DifficultyLocked");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderCenterX", 99)) {
|
||||
this.E = nbttagcompound.getDouble("BorderCenterX");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderCenterZ", 99)) {
|
||||
this.F = nbttagcompound.getDouble("BorderCenterZ");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderSize", 99)) {
|
||||
this.G = nbttagcompound.getDouble("BorderSize");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderSizeLerpTime", 99)) {
|
||||
this.H = nbttagcompound.getLong("BorderSizeLerpTime");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderSizeLerpTarget", 99)) {
|
||||
this.I = nbttagcompound.getDouble("BorderSizeLerpTarget");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderSafeZone", 99)) {
|
||||
this.J = nbttagcompound.getDouble("BorderSafeZone");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderDamagePerBlock", 99)) {
|
||||
this.K = nbttagcompound.getDouble("BorderDamagePerBlock");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderWarningBlocks", 99)) {
|
||||
this.L = nbttagcompound.getInt("BorderWarningBlocks");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("BorderWarningTime", 99)) {
|
||||
this.M = nbttagcompound.getInt("BorderWarningTime");
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("DimensionData", 10)) {
|
||||
nbttagcompound1 = nbttagcompound.getCompound("DimensionData");
|
||||
Iterator iterator = nbttagcompound1.c().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String s1 = (String) iterator.next();
|
||||
|
||||
this.N.put(DimensionManager.a(Integer.parseInt(s1)), nbttagcompound1.getCompound(s1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public WorldData(WorldSettings worldsettings, String s) {
|
||||
this.f = WorldType.NORMAL;
|
||||
this.g = "";
|
||||
this.G = 6.0E7D;
|
||||
this.J = 5.0D;
|
||||
this.K = 0.2D;
|
||||
this.L = 5;
|
||||
this.M = 15;
|
||||
this.N = Maps.newEnumMap(DimensionManager.class);
|
||||
this.O = new GameRules();
|
||||
this.a(worldsettings);
|
||||
this.levelName = s;
|
||||
this.C = WorldData.a;
|
||||
this.B = false;
|
||||
}
|
||||
|
||||
public void a(WorldSettings worldsettings) {
|
||||
this.e = worldsettings.d();
|
||||
this.x = worldsettings.e();
|
||||
this.y = worldsettings.g();
|
||||
this.z = worldsettings.f();
|
||||
this.f = worldsettings.h();
|
||||
this.g = worldsettings.j();
|
||||
this.A = worldsettings.i();
|
||||
}
|
||||
|
||||
public WorldData(WorldData worlddata) {
|
||||
this.f = WorldType.NORMAL;
|
||||
this.g = "";
|
||||
this.G = 6.0E7D;
|
||||
this.J = 5.0D;
|
||||
this.K = 0.2D;
|
||||
this.L = 5;
|
||||
this.M = 15;
|
||||
this.N = Maps.newEnumMap(DimensionManager.class);
|
||||
this.O = new GameRules();
|
||||
this.e = worlddata.e;
|
||||
this.f = worlddata.f;
|
||||
this.g = worlddata.g;
|
||||
this.x = worlddata.x;
|
||||
this.y = worlddata.y;
|
||||
this.h = worlddata.h;
|
||||
this.i = worlddata.i;
|
||||
this.j = worlddata.j;
|
||||
this.k = worlddata.k;
|
||||
this.l = worlddata.l;
|
||||
this.m = worlddata.m;
|
||||
this.n = worlddata.n;
|
||||
this.o = worlddata.o;
|
||||
this.p = worlddata.p;
|
||||
this.levelName = worlddata.levelName;
|
||||
this.r = worlddata.r;
|
||||
this.u = worlddata.u;
|
||||
this.t = worlddata.t;
|
||||
this.w = worlddata.w;
|
||||
this.v = worlddata.v;
|
||||
this.z = worlddata.z;
|
||||
this.A = worlddata.A;
|
||||
this.B = worlddata.B;
|
||||
this.O = worlddata.O;
|
||||
this.C = worlddata.C;
|
||||
this.D = worlddata.D;
|
||||
this.E = worlddata.E;
|
||||
this.F = worlddata.F;
|
||||
this.G = worlddata.G;
|
||||
this.H = worlddata.H;
|
||||
this.I = worlddata.I;
|
||||
this.J = worlddata.J;
|
||||
this.K = worlddata.K;
|
||||
this.M = worlddata.M;
|
||||
this.L = worlddata.L;
|
||||
}
|
||||
|
||||
public NBTTagCompound a(@Nullable NBTTagCompound nbttagcompound) {
|
||||
if (nbttagcompound == null) {
|
||||
nbttagcompound = this.o;
|
||||
}
|
||||
|
||||
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
|
||||
|
||||
this.a(nbttagcompound1, nbttagcompound);
|
||||
return nbttagcompound1;
|
||||
}
|
||||
|
||||
private void a(NBTTagCompound nbttagcompound, NBTTagCompound nbttagcompound1) {
|
||||
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
|
||||
|
||||
nbttagcompound2.setString("Name", "1.12.2");
|
||||
nbttagcompound2.setInt("Id", 1343);
|
||||
nbttagcompound2.setBoolean("Snapshot", false);
|
||||
nbttagcompound.set("Version", nbttagcompound2);
|
||||
nbttagcompound.setInt("DataVersion", 1343);
|
||||
nbttagcompound.setLong("RandomSeed", this.e);
|
||||
nbttagcompound.setString("generatorName", this.f.name());
|
||||
nbttagcompound.setInt("generatorVersion", this.f.getVersion());
|
||||
nbttagcompound.setString("generatorOptions", this.g);
|
||||
nbttagcompound.setInt("GameType", this.x.getId());
|
||||
nbttagcompound.setBoolean("MapFeatures", this.y);
|
||||
nbttagcompound.setInt("SpawnX", this.h);
|
||||
nbttagcompound.setInt("SpawnY", this.i);
|
||||
nbttagcompound.setInt("SpawnZ", this.j);
|
||||
nbttagcompound.setLong("Time", this.k);
|
||||
nbttagcompound.setLong("DayTime", this.l);
|
||||
nbttagcompound.setLong("SizeOnDisk", this.n);
|
||||
nbttagcompound.setLong("LastPlayed", MinecraftServer.aw());
|
||||
nbttagcompound.setString("LevelName", this.levelName);
|
||||
nbttagcompound.setInt("version", this.r);
|
||||
nbttagcompound.setInt("clearWeatherTime", this.s);
|
||||
nbttagcompound.setInt("rainTime", this.u);
|
||||
nbttagcompound.setBoolean("raining", this.t);
|
||||
nbttagcompound.setInt("thunderTime", this.w);
|
||||
nbttagcompound.setBoolean("thundering", this.v);
|
||||
nbttagcompound.setBoolean("hardcore", this.z);
|
||||
nbttagcompound.setBoolean("allowCommands", this.A);
|
||||
nbttagcompound.setBoolean("initialized", this.B);
|
||||
nbttagcompound.setDouble("BorderCenterX", this.E);
|
||||
nbttagcompound.setDouble("BorderCenterZ", this.F);
|
||||
nbttagcompound.setDouble("BorderSize", this.G);
|
||||
nbttagcompound.setLong("BorderSizeLerpTime", this.H);
|
||||
nbttagcompound.setDouble("BorderSafeZone", this.J);
|
||||
nbttagcompound.setDouble("BorderDamagePerBlock", this.K);
|
||||
nbttagcompound.setDouble("BorderSizeLerpTarget", this.I);
|
||||
nbttagcompound.setDouble("BorderWarningBlocks", this.L);
|
||||
nbttagcompound.setDouble("BorderWarningTime", this.M);
|
||||
if (this.C != null) {
|
||||
nbttagcompound.setByte("Difficulty", (byte) this.C.a());
|
||||
}
|
||||
|
||||
nbttagcompound.setBoolean("DifficultyLocked", this.D);
|
||||
nbttagcompound.set("GameRules", this.O.a());
|
||||
NBTTagCompound nbttagcompound3 = new NBTTagCompound();
|
||||
Iterator iterator = this.N.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entry entry = (Entry) iterator.next();
|
||||
|
||||
nbttagcompound3.set(String.valueOf(((DimensionManager) entry.getKey()).getDimensionID()), (NBTBase) entry.getValue());
|
||||
}
|
||||
|
||||
nbttagcompound.set("DimensionData", nbttagcompound3);
|
||||
if (nbttagcompound1 != null) {
|
||||
nbttagcompound.set("Player", nbttagcompound1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return this.e;
|
||||
}
|
||||
|
||||
public int b() {
|
||||
return this.h;
|
||||
}
|
||||
|
||||
public int c() {
|
||||
return this.i;
|
||||
}
|
||||
|
||||
public int d() {
|
||||
return this.j;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return this.k;
|
||||
}
|
||||
|
||||
public long getDayTime() {
|
||||
return this.l;
|
||||
}
|
||||
|
||||
public NBTTagCompound h() {
|
||||
return this.o;
|
||||
}
|
||||
|
||||
public void setTime(long i) {
|
||||
this.k = i;
|
||||
}
|
||||
|
||||
public void setDayTime(long i) {
|
||||
this.l = i;
|
||||
}
|
||||
|
||||
public void setSpawn(BlockPosition blockposition) {
|
||||
this.h = blockposition.getX();
|
||||
this.i = blockposition.getY();
|
||||
this.j = blockposition.getZ();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.levelName;
|
||||
}
|
||||
|
||||
public void a(String s) {
|
||||
this.levelName = s;
|
||||
}
|
||||
|
||||
public int k() {
|
||||
return this.r;
|
||||
}
|
||||
|
||||
public void e(int i) {
|
||||
this.r = i;
|
||||
}
|
||||
|
||||
public int z() {
|
||||
return this.s;
|
||||
}
|
||||
|
||||
public void i(int i) {
|
||||
this.s = i;
|
||||
}
|
||||
|
||||
public boolean isThundering() {
|
||||
return this.v;
|
||||
}
|
||||
|
||||
public void setThundering(boolean flag) {
|
||||
// CraftBukkit start
|
||||
org.bukkit.World world = Bukkit.getWorld(getName());
|
||||
if (world != null) {
|
||||
ThunderChangeEvent thunder = new ThunderChangeEvent(world, flag);
|
||||
Bukkit.getServer().getPluginManager().callEvent(thunder);
|
||||
if (thunder.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.v = flag;
|
||||
}
|
||||
|
||||
public int getThunderDuration() {
|
||||
return this.w;
|
||||
}
|
||||
|
||||
public void setThunderDuration(int i) {
|
||||
this.w = i;
|
||||
}
|
||||
|
||||
public boolean hasStorm() {
|
||||
return this.t;
|
||||
}
|
||||
|
||||
public void setStorm(boolean flag) {
|
||||
// CraftBukkit start
|
||||
org.bukkit.World world = Bukkit.getWorld(getName());
|
||||
if (world != null) {
|
||||
WeatherChangeEvent weather = new WeatherChangeEvent(world, flag);
|
||||
Bukkit.getServer().getPluginManager().callEvent(weather);
|
||||
if (weather.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.t = flag;
|
||||
}
|
||||
|
||||
public int getWeatherDuration() {
|
||||
return this.u;
|
||||
}
|
||||
|
||||
public void setWeatherDuration(int i) {
|
||||
this.u = i;
|
||||
}
|
||||
|
||||
public EnumGamemode getGameType() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public boolean shouldGenerateMapFeatures() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public void f(boolean flag) {
|
||||
this.y = flag;
|
||||
}
|
||||
|
||||
public void setGameType(EnumGamemode enumgamemode) {
|
||||
this.x = enumgamemode;
|
||||
}
|
||||
|
||||
public boolean isHardcore() {
|
||||
return this.z;
|
||||
}
|
||||
|
||||
public void g(boolean flag) {
|
||||
this.z = flag;
|
||||
}
|
||||
|
||||
public WorldType getType() {
|
||||
return this.f;
|
||||
}
|
||||
|
||||
public void a(WorldType worldtype) {
|
||||
this.f = worldtype;
|
||||
}
|
||||
|
||||
public String getGeneratorOptions() {
|
||||
return this.g == null ? "" : this.g;
|
||||
}
|
||||
|
||||
public boolean u() {
|
||||
return this.A;
|
||||
}
|
||||
|
||||
public void c(boolean flag) {
|
||||
this.A = flag;
|
||||
}
|
||||
|
||||
public boolean v() {
|
||||
return this.B;
|
||||
}
|
||||
|
||||
public void d(boolean flag) {
|
||||
this.B = flag;
|
||||
}
|
||||
|
||||
public GameRules w() {
|
||||
return this.O;
|
||||
}
|
||||
|
||||
public double B() {
|
||||
return this.E;
|
||||
}
|
||||
|
||||
public double C() {
|
||||
return this.F;
|
||||
}
|
||||
|
||||
public double D() {
|
||||
return this.G;
|
||||
}
|
||||
|
||||
public void a(double d0) {
|
||||
this.G = d0;
|
||||
}
|
||||
|
||||
public long E() {
|
||||
return this.H;
|
||||
}
|
||||
|
||||
public void e(long i) {
|
||||
this.H = i;
|
||||
}
|
||||
|
||||
public double F() {
|
||||
return this.I;
|
||||
}
|
||||
|
||||
public void b(double d0) {
|
||||
this.I = d0;
|
||||
}
|
||||
|
||||
public void c(double d0) {
|
||||
this.F = d0;
|
||||
}
|
||||
|
||||
public void d(double d0) {
|
||||
this.E = d0;
|
||||
}
|
||||
|
||||
public double G() {
|
||||
return this.J;
|
||||
}
|
||||
|
||||
public void e(double d0) {
|
||||
this.J = d0;
|
||||
}
|
||||
|
||||
public double H() {
|
||||
return this.K;
|
||||
}
|
||||
|
||||
public void f(double d0) {
|
||||
this.K = d0;
|
||||
}
|
||||
|
||||
public int I() {
|
||||
return this.L;
|
||||
}
|
||||
|
||||
public int J() {
|
||||
return this.M;
|
||||
}
|
||||
|
||||
public void j(int i) {
|
||||
this.L = i;
|
||||
}
|
||||
|
||||
public void k(int i) {
|
||||
this.M = i;
|
||||
}
|
||||
|
||||
public EnumDifficulty getDifficulty() {
|
||||
return this.C;
|
||||
}
|
||||
|
||||
public void setDifficulty(EnumDifficulty enumdifficulty) {
|
||||
this.C = enumdifficulty;
|
||||
// CraftBukkit start
|
||||
PacketPlayOutServerDifficulty packet = new PacketPlayOutServerDifficulty(this.getDifficulty(), this.isDifficultyLocked());
|
||||
for (EntityPlayer player : (java.util.List<EntityPlayer>) (java.util.List) world.players) {
|
||||
player.playerConnection.sendPacket(packet);
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
public boolean isDifficultyLocked() {
|
||||
return this.D;
|
||||
}
|
||||
|
||||
public void e(boolean flag) {
|
||||
this.D = flag;
|
||||
}
|
||||
|
||||
public void a(CrashReportSystemDetails crashreportsystemdetails) {
|
||||
crashreportsystemdetails.a("Level seed", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.valueOf(WorldData.this.getSeed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level generator", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.format("ID %02d - %s, ver %d. Features enabled: %b", new Object[] { Integer.valueOf(WorldData.this.f.g()), WorldData.this.f.name(), Integer.valueOf(WorldData.this.f.getVersion()), Boolean.valueOf(WorldData.this.y)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level generator options", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return WorldData.this.g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level spawn location", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return CrashReportSystemDetails.a(WorldData.this.h, WorldData.this.i, WorldData.this.j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level time", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.format("%d game time, %d day time", new Object[] { Long.valueOf(WorldData.this.k), Long.valueOf(WorldData.this.l)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level dimension", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.valueOf(WorldData.this.p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level storage version", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
String s = "Unknown?";
|
||||
|
||||
try {
|
||||
switch (WorldData.this.r) {
|
||||
case 19132:
|
||||
s = "McRegion";
|
||||
break;
|
||||
|
||||
case 19133:
|
||||
s = "Anvil";
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
;
|
||||
}
|
||||
|
||||
return String.format("0x%05X - %s", new Object[] { Integer.valueOf(WorldData.this.r), s});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level weather", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.format("Rain time: %d (now: %b), thunder time: %d (now: %b)", new Object[] { Integer.valueOf(WorldData.this.u), Boolean.valueOf(WorldData.this.t), Integer.valueOf(WorldData.this.w), Boolean.valueOf(WorldData.this.v)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
crashreportsystemdetails.a("Level game mode", new CrashReportCallable() {
|
||||
public String a() throws Exception {
|
||||
return String.format("Game mode: %s (ID %d). Hardcore: %b. Cheats: %b", new Object[] { WorldData.this.x.b(), Integer.valueOf(WorldData.this.x.getId()), Boolean.valueOf(WorldData.this.z), Boolean.valueOf(WorldData.this.A)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return this.a();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public NBTTagCompound a(DimensionManager dimensionmanager) {
|
||||
NBTTagCompound nbttagcompound = this.N.get(dimensionmanager);
|
||||
|
||||
return nbttagcompound == null ? new NBTTagCompound() : nbttagcompound;
|
||||
}
|
||||
|
||||
public void a(DimensionManager dimensionmanager, NBTTagCompound nbttagcompound) {
|
||||
this.N.put(dimensionmanager, nbttagcompound);
|
||||
}
|
||||
|
||||
// CraftBukkit start - Check if the name stored in NBT is the correct one
|
||||
public void checkName( String name ) {
|
||||
if ( !this.levelName.equals( name ) ) {
|
||||
this.levelName = name;
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
2119
sources/src/main/java/org/bukkit/craftbukkit/CraftServer.java
Normal file
2119
sources/src/main/java/org/bukkit/craftbukkit/CraftServer.java
Normal file
File diff suppressed because it is too large
Load Diff
2041
sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
Normal file
2041
sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
Normal file
File diff suppressed because it is too large
Load Diff
791
sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java
Normal file
791
sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java
Normal file
@@ -0,0 +1,791 @@
|
||||
package org.bukkit.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.destroystokyo.paper.event.server.ServerExceptionEvent;
|
||||
import com.destroystokyo.paper.exception.ServerEventException;
|
||||
import com.destroystokyo.paper.exception.ServerPluginEnableDisableException;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.PluginCommandYamlParser;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.permissions.Permissible;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.util.FileUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
|
||||
/**
|
||||
* Handles all plugin management from the Server
|
||||
*/
|
||||
public final class SimplePluginManager implements PluginManager {
|
||||
private final Server server;
|
||||
private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>();
|
||||
private final List<Plugin> plugins = new ArrayList<Plugin>();
|
||||
private final Map<String, Plugin> lookupNames = new HashMap<String, Plugin>();
|
||||
private File updateDirectory;
|
||||
private final SimpleCommandMap commandMap;
|
||||
private final Map<String, Permission> permissions = new HashMap<String, Permission>();
|
||||
private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>();
|
||||
private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>();
|
||||
private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>();
|
||||
private boolean useTimings = false;
|
||||
|
||||
public SimplePluginManager(Server instance, SimpleCommandMap commandMap) {
|
||||
server = instance;
|
||||
this.commandMap = commandMap;
|
||||
|
||||
defaultPerms.put(true, new HashSet<Permission>());
|
||||
defaultPerms.put(false, new HashSet<Permission>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the specified plugin loader
|
||||
*
|
||||
* @param loader Class name of the PluginLoader to register
|
||||
* @throws IllegalArgumentException Thrown when the given Class is not a
|
||||
* valid PluginLoader
|
||||
*/
|
||||
public void registerInterface(Class<? extends PluginLoader> loader) throws IllegalArgumentException {
|
||||
PluginLoader instance;
|
||||
|
||||
if (PluginLoader.class.isAssignableFrom(loader)) {
|
||||
Constructor<? extends PluginLoader> constructor;
|
||||
|
||||
try {
|
||||
constructor = loader.getConstructor(Server.class);
|
||||
instance = constructor.newInstance(server);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
String className = loader.getName();
|
||||
|
||||
throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName()));
|
||||
}
|
||||
|
||||
Pattern[] patterns = instance.getPluginFileFilters();
|
||||
|
||||
synchronized (this) {
|
||||
for (Pattern pattern : patterns) {
|
||||
fileAssociations.put(pattern, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plugins contained within the specified directory
|
||||
*
|
||||
* @param directory Directory to check for plugins
|
||||
* @return A list of all plugins loaded
|
||||
*/
|
||||
public Plugin[] loadPlugins(File directory) {
|
||||
Validate.notNull(directory, "Directory cannot be null");
|
||||
Validate.isTrue(directory.isDirectory(), "Directory must be a directory");
|
||||
|
||||
List<Plugin> result = new ArrayList<Plugin>();
|
||||
Set<Pattern> filters = fileAssociations.keySet();
|
||||
|
||||
if (!(server.getUpdateFolder().equals(""))) {
|
||||
updateDirectory = new File(directory, server.getUpdateFolder());
|
||||
}
|
||||
|
||||
Map<String, File> plugins = new HashMap<String, File>();
|
||||
Set<String> loadedPlugins = new HashSet<String>();
|
||||
Map<String, Collection<String>> dependencies = new HashMap<String, Collection<String>>();
|
||||
Map<String, Collection<String>> softDependencies = new HashMap<String, Collection<String>>();
|
||||
|
||||
// This is where it figures out all possible plugins
|
||||
for (File file : directory.listFiles()) {
|
||||
PluginLoader loader = null;
|
||||
for (Pattern filter : filters) {
|
||||
Matcher match = filter.matcher(file.getName());
|
||||
if (match.find()) {
|
||||
loader = fileAssociations.get(filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (loader == null) continue;
|
||||
|
||||
PluginDescriptionFile description = null;
|
||||
try {
|
||||
description = loader.getPluginDescription(file);
|
||||
String name = description.getName();
|
||||
if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name");
|
||||
continue;
|
||||
} else if (description.rawName.indexOf(' ') != -1) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': uses the space-character (0x20) in its name");
|
||||
continue;
|
||||
}
|
||||
} catch (InvalidDescriptionException ex) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
File replacedFile = plugins.put(description.getName(), file);
|
||||
if (replacedFile != null) {
|
||||
server.getLogger().severe(String.format(
|
||||
"Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'",
|
||||
description.getName(),
|
||||
file.getPath(),
|
||||
replacedFile.getPath(),
|
||||
directory.getPath()
|
||||
));
|
||||
}
|
||||
|
||||
Collection<String> softDependencySet = description.getSoftDepend();
|
||||
if (softDependencySet != null && !softDependencySet.isEmpty()) {
|
||||
if (softDependencies.containsKey(description.getName())) {
|
||||
// Duplicates do not matter, they will be removed together if applicable
|
||||
softDependencies.get(description.getName()).addAll(softDependencySet);
|
||||
} else {
|
||||
softDependencies.put(description.getName(), new LinkedList<String>(softDependencySet));
|
||||
}
|
||||
}
|
||||
|
||||
Collection<String> dependencySet = description.getDepend();
|
||||
if (dependencySet != null && !dependencySet.isEmpty()) {
|
||||
dependencies.put(description.getName(), new LinkedList<String>(dependencySet));
|
||||
}
|
||||
|
||||
Collection<String> loadBeforeSet = description.getLoadBefore();
|
||||
if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) {
|
||||
for (String loadBeforeTarget : loadBeforeSet) {
|
||||
if (softDependencies.containsKey(loadBeforeTarget)) {
|
||||
softDependencies.get(loadBeforeTarget).add(description.getName());
|
||||
} else {
|
||||
// softDependencies is never iterated, so 'ghost' plugins aren't an issue
|
||||
Collection<String> shortSoftDependency = new LinkedList<String>();
|
||||
shortSoftDependency.add(description.getName());
|
||||
softDependencies.put(loadBeforeTarget, shortSoftDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!plugins.isEmpty()) {
|
||||
boolean missingDependency = true;
|
||||
Iterator<Map.Entry<String, File>> pluginIterator = plugins.entrySet().iterator();
|
||||
|
||||
while (pluginIterator.hasNext()) {
|
||||
Map.Entry<String, File> entry = pluginIterator.next();
|
||||
String plugin = entry.getKey();
|
||||
|
||||
if (dependencies.containsKey(plugin)) {
|
||||
Iterator<String> dependencyIterator = dependencies.get(plugin).iterator();
|
||||
|
||||
while (dependencyIterator.hasNext()) {
|
||||
String dependency = dependencyIterator.next();
|
||||
|
||||
// Dependency loaded
|
||||
if (loadedPlugins.contains(dependency)) {
|
||||
dependencyIterator.remove();
|
||||
|
||||
// We have a dependency not found
|
||||
} else if (!plugins.containsKey(dependency)) {
|
||||
missingDependency = false;
|
||||
pluginIterator.remove();
|
||||
softDependencies.remove(plugin);
|
||||
dependencies.remove(plugin);
|
||||
|
||||
server.getLogger().log(
|
||||
Level.SEVERE,
|
||||
"Could not load '" + entry.getValue().getPath() + "' in folder '" + directory.getPath() + "'",
|
||||
new UnknownDependencyException(dependency));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
|
||||
dependencies.remove(plugin);
|
||||
}
|
||||
}
|
||||
if (softDependencies.containsKey(plugin)) {
|
||||
Iterator<String> softDependencyIterator = softDependencies.get(plugin).iterator();
|
||||
|
||||
while (softDependencyIterator.hasNext()) {
|
||||
String softDependency = softDependencyIterator.next();
|
||||
|
||||
// Soft depend is no longer around
|
||||
if (!plugins.containsKey(softDependency)) {
|
||||
softDependencyIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (softDependencies.get(plugin).isEmpty()) {
|
||||
softDependencies.remove(plugin);
|
||||
}
|
||||
}
|
||||
if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) {
|
||||
// We're clear to load, no more soft or hard dependencies left
|
||||
File file = plugins.get(plugin);
|
||||
pluginIterator.remove();
|
||||
missingDependency = false;
|
||||
|
||||
try {
|
||||
result.add(loadPlugin(file));
|
||||
loadedPlugins.add(plugin);
|
||||
continue;
|
||||
} catch (InvalidPluginException ex) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingDependency) {
|
||||
// We now iterate over plugins until something loads
|
||||
// This loop will ignore soft dependencies
|
||||
pluginIterator = plugins.entrySet().iterator();
|
||||
|
||||
while (pluginIterator.hasNext()) {
|
||||
Map.Entry<String, File> entry = pluginIterator.next();
|
||||
String plugin = entry.getKey();
|
||||
|
||||
if (!dependencies.containsKey(plugin)) {
|
||||
softDependencies.remove(plugin);
|
||||
missingDependency = false;
|
||||
File file = entry.getValue();
|
||||
pluginIterator.remove();
|
||||
|
||||
try {
|
||||
result.add(loadPlugin(file));
|
||||
loadedPlugins.add(plugin);
|
||||
break;
|
||||
} catch (InvalidPluginException ex) {
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
// We have no plugins left without a depend
|
||||
if (missingDependency) {
|
||||
softDependencies.clear();
|
||||
dependencies.clear();
|
||||
Iterator<File> failedPluginIterator = plugins.values().iterator();
|
||||
|
||||
while (failedPluginIterator.hasNext()) {
|
||||
File file = failedPluginIterator.next();
|
||||
failedPluginIterator.remove();
|
||||
server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toArray(new Plugin[result.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plugin in the specified file
|
||||
* <p>
|
||||
* File must be valid according to the current enabled Plugin interfaces
|
||||
*
|
||||
* @param file File containing the plugin to load
|
||||
* @return The Plugin loaded, or null if it was invalid
|
||||
* @throws InvalidPluginException Thrown when the specified file is not a
|
||||
* valid plugin
|
||||
* @throws UnknownDependencyException If a required dependency could not
|
||||
* be found
|
||||
*/
|
||||
public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException {
|
||||
Validate.notNull(file, "File cannot be null");
|
||||
|
||||
checkUpdate(file);
|
||||
|
||||
Set<Pattern> filters = fileAssociations.keySet();
|
||||
Plugin result = null;
|
||||
|
||||
for (Pattern filter : filters) {
|
||||
String name = file.getName();
|
||||
Matcher match = filter.matcher(name);
|
||||
|
||||
if (match.find()) {
|
||||
PluginLoader loader = fileAssociations.get(filter);
|
||||
|
||||
result = loader.loadPlugin(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
plugins.add(result);
|
||||
lookupNames.put(result.getDescription().getName().toLowerCase(java.util.Locale.ENGLISH), result); // Spigot
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkUpdate(File file) {
|
||||
if (updateDirectory == null || !updateDirectory.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File updateFile = new File(updateDirectory, file.getName());
|
||||
if (updateFile.isFile() && FileUtil.copy(updateFile, file)) {
|
||||
updateFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin is loaded and returns it when applicable
|
||||
* <p>
|
||||
* Please note that the name of the plugin is case-sensitive
|
||||
*
|
||||
* @param name Name of the plugin to check
|
||||
* @return Plugin if it exists, otherwise null
|
||||
*/
|
||||
public synchronized Plugin getPlugin(String name) {
|
||||
return lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Spigot
|
||||
}
|
||||
|
||||
public synchronized Plugin[] getPlugins() {
|
||||
return plugins.toArray(new Plugin[plugins.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin is enabled or not
|
||||
* <p>
|
||||
* Please note that the name of the plugin is case-sensitive.
|
||||
*
|
||||
* @param name Name of the plugin to check
|
||||
* @return true if the plugin is enabled, otherwise false
|
||||
*/
|
||||
public boolean isPluginEnabled(String name) {
|
||||
Plugin plugin = getPlugin(name);
|
||||
|
||||
return isPluginEnabled(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin is enabled or not
|
||||
*
|
||||
* @param plugin Plugin to check
|
||||
* @return true if the plugin is enabled, otherwise false
|
||||
*/
|
||||
public synchronized boolean isPluginEnabled(Plugin plugin) { // Paper - synchronize
|
||||
if ((plugin != null) && (plugins.contains(plugin))) {
|
||||
return plugin.isEnabled();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void enablePlugin(final Plugin plugin) { // Paper - synchronize
|
||||
if (!plugin.isEnabled()) {
|
||||
List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
|
||||
|
||||
if (!pluginCommands.isEmpty()) {
|
||||
commandMap.registerAll(plugin.getDescription().getName(), pluginCommands);
|
||||
}
|
||||
|
||||
try {
|
||||
plugin.getPluginLoader().enablePlugin(plugin);
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while enabling "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin);
|
||||
}
|
||||
|
||||
HandlerList.bakeAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Paper start - close Classloader on disable
|
||||
public void disablePlugins() {
|
||||
disablePlugins(false);
|
||||
}
|
||||
|
||||
public void disablePlugins(boolean closeClassloaders) {
|
||||
// Paper end - close Classloader on disable
|
||||
Plugin[] plugins = getPlugins();
|
||||
for (int i = plugins.length - 1; i >= 0; i--) {
|
||||
disablePlugin(plugins[i], closeClassloaders); // Paper - close Classloader on disable
|
||||
}
|
||||
}
|
||||
|
||||
// Paper start - close Classloader on disable
|
||||
public void disablePlugin(Plugin plugin) {
|
||||
disablePlugin(plugin, false);
|
||||
}
|
||||
|
||||
public synchronized void disablePlugin(final Plugin plugin, boolean closeClassloader) { // Paper - synchronize
|
||||
// Paper end - close Classloader on disable
|
||||
if (plugin.isEnabled()) {
|
||||
try {
|
||||
plugin.getPluginLoader().disablePlugin(plugin, closeClassloader); // Paper - close Classloader on disable
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while disabling "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper
|
||||
}
|
||||
|
||||
try {
|
||||
server.getScheduler().cancelTasks(plugin);
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper
|
||||
}
|
||||
|
||||
try {
|
||||
server.getServicesManager().unregisterAll(plugin);
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while unregistering services for "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper
|
||||
}
|
||||
|
||||
try {
|
||||
HandlerList.unregisterAll(plugin);
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while unregistering events for "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper
|
||||
}
|
||||
|
||||
try {
|
||||
server.getMessenger().unregisterIncomingPluginChannel(plugin);
|
||||
server.getMessenger().unregisterOutgoingPluginChannel(plugin);
|
||||
} catch (Throwable ex) {
|
||||
handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for "
|
||||
+ plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Paper start
|
||||
private void handlePluginException(String msg, Throwable ex, Plugin plugin) {
|
||||
server.getLogger().log(Level.SEVERE, msg, ex);
|
||||
callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin)));
|
||||
}
|
||||
// Paper end
|
||||
|
||||
public void clearPlugins() {
|
||||
synchronized (this) {
|
||||
disablePlugins(true); // Paper - close Classloader on disable
|
||||
plugins.clear();
|
||||
lookupNames.clear();
|
||||
HandlerList.unregisterAll();
|
||||
fileAssociations.clear();
|
||||
permissions.clear();
|
||||
defaultPerms.get(true).clear();
|
||||
defaultPerms.get(false).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an event with the given details.
|
||||
* <p>
|
||||
* This method only synchronizes when the event is not asynchronous.
|
||||
*
|
||||
* @param event Event details
|
||||
*/
|
||||
public void callEvent(Event event) {
|
||||
if (event.isAsynchronous()) {
|
||||
if (Thread.holdsLock(this)) {
|
||||
throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
|
||||
}
|
||||
if (server.isPrimaryThread()) {
|
||||
throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
|
||||
}
|
||||
fireEvent(event);
|
||||
} else {
|
||||
Akari.eventLock.lock(); // Akarin
|
||||
synchronized (this) {
|
||||
fireEvent(event);
|
||||
}
|
||||
Akari.eventLock.unlock(); // Akarin
|
||||
}
|
||||
}
|
||||
|
||||
private void fireEvent(Event event) {
|
||||
HandlerList handlers = event.getHandlers();
|
||||
RegisteredListener[] listeners = handlers.getRegisteredListeners();
|
||||
|
||||
for (RegisteredListener registration : listeners) {
|
||||
if (!registration.getPlugin().isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
registration.callEvent(event);
|
||||
} catch (AuthorNagException ex) {
|
||||
Plugin plugin = registration.getPlugin();
|
||||
|
||||
if (plugin.isNaggable()) {
|
||||
plugin.setNaggable(false);
|
||||
|
||||
server.getLogger().log(Level.SEVERE, String.format(
|
||||
"Nag author(s): '%s' of '%s' about the following: %s",
|
||||
plugin.getDescription().getAuthors(),
|
||||
plugin.getDescription().getFullName(),
|
||||
ex.getMessage()
|
||||
));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
// Paper start - error reporting
|
||||
String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName();
|
||||
server.getLogger().log(Level.SEVERE, msg, ex);
|
||||
if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop
|
||||
callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event)));
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerEvents(Listener listener, Plugin plugin) {
|
||||
if (!plugin.isEnabled()) {
|
||||
throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
|
||||
}
|
||||
|
||||
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
|
||||
getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) {
|
||||
registerEvent(event, listener, priority, executor, plugin, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given event to the specified listener using a directly
|
||||
* passed EventExecutor
|
||||
*
|
||||
* @param event Event class to register
|
||||
* @param listener PlayerListener to register
|
||||
* @param priority Priority of this event
|
||||
* @param executor EventExecutor to register
|
||||
* @param plugin Plugin to register
|
||||
* @param ignoreCancelled Do not call executor if event was already
|
||||
* cancelled
|
||||
*/
|
||||
public void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) {
|
||||
Validate.notNull(listener, "Listener cannot be null");
|
||||
Validate.notNull(priority, "Priority cannot be null");
|
||||
Validate.notNull(executor, "Executor cannot be null");
|
||||
Validate.notNull(plugin, "Plugin cannot be null");
|
||||
|
||||
if (!plugin.isEnabled()) {
|
||||
throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
|
||||
}
|
||||
|
||||
executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Spigot
|
||||
if (false) { // Spigot - RL handles useTimings check now
|
||||
getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
||||
} else {
|
||||
getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
||||
}
|
||||
}
|
||||
|
||||
private HandlerList getEventListeners(Class<? extends Event> type) {
|
||||
try {
|
||||
Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList");
|
||||
method.setAccessible(true);
|
||||
return (HandlerList) method.invoke(null);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalPluginAccessException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) {
|
||||
try {
|
||||
clazz.getDeclaredMethod("getHandlerList");
|
||||
return clazz;
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (clazz.getSuperclass() != null
|
||||
&& !clazz.getSuperclass().equals(Event.class)
|
||||
&& Event.class.isAssignableFrom(clazz.getSuperclass())) {
|
||||
return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));
|
||||
} else {
|
||||
throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Permission getPermission(String name) {
|
||||
return permissions.get(name.toLowerCase(java.util.Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public void addPermission(Permission perm) {
|
||||
addPermission(perm, true);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void addPermission(Permission perm, boolean dirty) {
|
||||
String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH);
|
||||
|
||||
if (permissions.containsKey(name)) {
|
||||
throw new IllegalArgumentException("The permission " + name + " is already defined!");
|
||||
}
|
||||
|
||||
permissions.put(name, perm);
|
||||
calculatePermissionDefault(perm, dirty);
|
||||
}
|
||||
|
||||
public Set<Permission> getDefaultPermissions(boolean op) {
|
||||
return ImmutableSet.copyOf(defaultPerms.get(op));
|
||||
}
|
||||
|
||||
public void removePermission(Permission perm) {
|
||||
removePermission(perm.getName());
|
||||
}
|
||||
|
||||
public void removePermission(String name) {
|
||||
permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public void recalculatePermissionDefaults(Permission perm) {
|
||||
if (perm != null && permissions.containsKey(perm.getName().toLowerCase(java.util.Locale.ENGLISH))) {
|
||||
defaultPerms.get(true).remove(perm);
|
||||
defaultPerms.get(false).remove(perm);
|
||||
|
||||
calculatePermissionDefault(perm, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculatePermissionDefault(Permission perm, boolean dirty) {
|
||||
if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
|
||||
defaultPerms.get(true).add(perm);
|
||||
if (dirty) {
|
||||
dirtyPermissibles(true);
|
||||
}
|
||||
}
|
||||
if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
|
||||
defaultPerms.get(false).add(perm);
|
||||
if (dirty) {
|
||||
dirtyPermissibles(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void dirtyPermissibles() {
|
||||
dirtyPermissibles(true);
|
||||
dirtyPermissibles(false);
|
||||
}
|
||||
|
||||
private void dirtyPermissibles(boolean op) {
|
||||
Set<Permissible> permissibles = getDefaultPermSubscriptions(op);
|
||||
|
||||
for (Permissible p : permissibles) {
|
||||
p.recalculatePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribeToPermission(String permission, Permissible permissible) {
|
||||
String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||
|
||||
if (map == null) {
|
||||
map = new WeakHashMap<Permissible, Boolean>();
|
||||
permSubs.put(name, map);
|
||||
}
|
||||
|
||||
map.put(permissible, true);
|
||||
}
|
||||
|
||||
public void unsubscribeFromPermission(String permission, Permissible permissible) {
|
||||
String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||
|
||||
if (map != null) {
|
||||
map.remove(permissible);
|
||||
|
||||
if (map.isEmpty()) {
|
||||
permSubs.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Permissible> getPermissionSubscriptions(String permission) {
|
||||
String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||
|
||||
if (map == null) {
|
||||
return ImmutableSet.of();
|
||||
} else {
|
||||
return ImmutableSet.copyOf(map.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribeToDefaultPerms(boolean op, Permissible permissible) {
|
||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||
|
||||
if (map == null) {
|
||||
map = new WeakHashMap<Permissible, Boolean>();
|
||||
defSubs.put(op, map);
|
||||
}
|
||||
|
||||
map.put(permissible, true);
|
||||
}
|
||||
|
||||
public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) {
|
||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||
|
||||
if (map != null) {
|
||||
map.remove(permissible);
|
||||
|
||||
if (map.isEmpty()) {
|
||||
defSubs.remove(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
|
||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||
|
||||
if (map == null) {
|
||||
return ImmutableSet.of();
|
||||
} else {
|
||||
return ImmutableSet.copyOf(map.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Permission> getPermissions() {
|
||||
return new HashSet<Permission>(permissions.values());
|
||||
}
|
||||
|
||||
public boolean useTimings() {
|
||||
return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not per event timing code should be used
|
||||
*
|
||||
* @param use True if per event timing code should be used
|
||||
*/
|
||||
public void useTimings(boolean use) {
|
||||
co.aikar.timings.Timings.setTimingsEnabled(use); // Spigot
|
||||
}
|
||||
|
||||
// Paper start
|
||||
public void clearPermissions() {
|
||||
permissions.clear();
|
||||
defaultPerms.get(true).clear();
|
||||
defaultPerms.get(false).clear();
|
||||
}
|
||||
// Paper end
|
||||
|
||||
}
|
||||
37
sources/src/main/resources/configurations/bukkit.yml
Normal file
37
sources/src/main/resources/configurations/bukkit.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
# This is the main configuration file for Bukkit.
|
||||
# As you can see, there's actually not that much to configure without any plugins.
|
||||
# For a reference for any variable inside this file, check out the Bukkit Wiki at
|
||||
# http://wiki.bukkit.org/Bukkit.yml
|
||||
#
|
||||
# If you need help on this file, feel free to join us on irc or leave a message
|
||||
# on the forums asking for advice.
|
||||
#
|
||||
# IRC: #spigot @ irc.spi.gt
|
||||
# (If this means nothing to you, just go to http://www.spigotmc.org/pages/irc/ )
|
||||
# Forums: http://www.spigotmc.org/
|
||||
# Bug tracker: http://www.spigotmc.org/go/bugs
|
||||
|
||||
|
||||
settings:
|
||||
allow-end: true
|
||||
warn-on-overload: true
|
||||
permissions-file: permissions.yml
|
||||
update-folder: update
|
||||
plugin-profiling: false
|
||||
connection-throttle: 4000
|
||||
query-plugins: true
|
||||
deprecated-verbose: default
|
||||
shutdown-message: "Server closed"
|
||||
spawn-limits:
|
||||
monsters: 70
|
||||
animals: 15
|
||||
water-animals: 5
|
||||
ambient: 15
|
||||
chunk-gc:
|
||||
period-in-ticks: 600
|
||||
load-threshold: 0
|
||||
ticks-per:
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
autosave: 6000
|
||||
aliases: now-in-commands.yml
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0",
|
||||
"minVersion": "0.7.10",
|
||||
"package": "io.akarin.server.mixin",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
@@ -9,24 +9,26 @@
|
||||
"bootstrap.Bootstrap",
|
||||
"bootstrap.DummyEula",
|
||||
"bootstrap.MixinMetrics",
|
||||
"bootstrap.ParallelRegistry",
|
||||
"bootstrap.MetricsBootstrap",
|
||||
"bootstrap.MixinRestartCommand",
|
||||
|
||||
"core.MixinMCUtil",
|
||||
"core.DesyncCatcher",
|
||||
"core.MixinCraftServer",
|
||||
"core.MixinWorldServer",
|
||||
"core.MixinFileIOThread",
|
||||
"core.MixinWorldManager",
|
||||
"core.MixinChunkSection",
|
||||
"core.MixinAsyncCatcher",
|
||||
"core.MixinTimingHandler",
|
||||
"core.MonsterEggGuardian",
|
||||
"core.MixinVersionCommand",
|
||||
"core.MixinMinecraftServer",
|
||||
"core.MixinChunkIOExecutor",
|
||||
"core.MixinPlayerConnectionUtils",
|
||||
|
||||
"nsc.MixinPlayerConnection",
|
||||
"nsc.OptimisticNetworkManager",
|
||||
"nsc.NonblockingServerConnection",
|
||||
|
||||
"optimization.WeakBigTree",
|
||||
"optimization.WeakEnchantmentManager",
|
||||
"optimization.MixinEntity",
|
||||
"optimization.MixinEntityHorseAbstract",
|
||||
"optimization.MixinEntityTameableAnimal",
|
||||
"optimization.MixinTileEntityEnchantTable"
|
||||
|
||||
Submodule work/Paper updated: 960b6d19cb...77de933b88
Reference in New Issue
Block a user