Compare commits
45 Commits
ver/1.13
...
parallel-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d2d285d9 | ||
|
|
818f5559f7 | ||
|
|
bf7f6fe3bc | ||
|
|
7acc339704 | ||
|
|
f0d17e54e9 | ||
|
|
01c40ed0d3 | ||
|
|
4fdc9f0166 | ||
|
|
3b4926bed2 | ||
|
|
7e0f44f0af | ||
|
|
b41e4fd8c3 | ||
|
|
241a8ffefa | ||
|
|
c2748ea2df | ||
|
|
e563233ff3 | ||
|
|
9a5de6cf31 | ||
|
|
9f7b490f61 | ||
|
|
6dceb465c3 | ||
|
|
17ba164cbc | ||
|
|
bb19cf0f7a | ||
|
|
0a59c8ce5f | ||
|
|
5d5bb381c0 | ||
|
|
05cc09ff51 | ||
|
|
2ba4bc2755 | ||
|
|
9b5b40c002 | ||
|
|
a0545a756d | ||
|
|
6e62515d11 | ||
|
|
ff100c348e | ||
|
|
a25ff5dd93 | ||
|
|
c6dbae3c24 | ||
|
|
b2bafb826f | ||
|
|
fb20bb3113 | ||
|
|
927d946dba | ||
|
|
9ceec10f18 | ||
|
|
74353989e4 | ||
|
|
c2705d4722 | ||
|
|
b743d7dc4c | ||
|
|
601ec9e90d | ||
|
|
6dcd61f2c7 | ||
|
|
c42528f4f1 | ||
|
|
402d9d2536 | ||
|
|
6ba139a83e | ||
|
|
0690cb1c9f | ||
|
|
b8879db641 | ||
|
|
a3cc6062b6 | ||
|
|
dba9fad220 | ||
|
|
af7444df09 |
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
|
||||
17
README.md
17
README.md
@@ -1,8 +1,8 @@
|
||||
# <img src="https://i.loli.net/2018/05/17/5afd869c443ef.png" alt="Akarin Face" align="right">Akarin
|
||||
[](https://akarin.io)
|
||||
[](https://discord.gg/fw2pJAj)
|
||||
[](https://bstats.org/plugin/bukkit/Torch)
|
||||
[](http://ci.ilummc.com/job/Akarin/)
|
||||
[](https://circleci.com/gh/Akarin-project/Akarin/tree/master)
|
||||
[](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.12.2)
|
||||
|
||||
Akarin is currently **under heavy development** and contributions are welcome!
|
||||
|
||||
@@ -24,8 +24,8 @@ Get Akarin
|
||||
---
|
||||
### Download
|
||||
#### Recommended
|
||||
+ [**Jenkins**](http://ci.ilummc.com/job/Akarin/) - Kudos to [Izzel_Aliz](https://github.com/IzzelAliz)
|
||||
+ [**Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/master) - Checkout the 'Artifacts' tab of the latest build
|
||||
+ ~~[**Jenkins**](http://ci.ilummc.com/job/Akarin/)~~ - Kudos to [Izzel_Aliz](https://github.com/IzzelAliz)
|
||||
+ [**Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.12.2) - Checkout the 'Artifacts' tab of the latest build
|
||||
|
||||
*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*
|
||||
|
||||
@@ -47,6 +47,7 @@ Get Akarin
|
||||
Demo Servers
|
||||
---
|
||||
* `demo.akarin.io` (official)
|
||||
* `omc.hk` (auth required)
|
||||
|
||||
*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*
|
||||
|
||||
@@ -54,12 +55,6 @@ Contributing
|
||||
---
|
||||
* 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 us an email with your experience and necessary information.
|
||||
|
||||
Contact
|
||||
---
|
||||
[Discord](https://discord.gg/D3Rsukh)
|
||||
|
||||
Email: `kira@kira.moe`
|
||||
* 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-*.jar $CIRCLE_ARTIFACTS
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>akarin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.12.2-R0.4-RELEASE</version>
|
||||
<version>1.12.2-R0.4-SNAPSHOT</version>
|
||||
<name>Akarin</name>
|
||||
<url>https://github.com/Akarin-project/Akarin</url>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<dependency>
|
||||
<groupId>io.akarin</groupId>
|
||||
<artifactId>legacylauncher</artifactId>
|
||||
<version>1.25</version>
|
||||
<version>1.26</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* 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.util.LoadingIntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add volatile to fields (safety issue)
|
||||
*/
|
||||
class TimingHandler implements Timing {
|
||||
|
||||
private static int idPool = 1;
|
||||
final int id = idPool++;
|
||||
|
||||
final String name;
|
||||
private final boolean verbose;
|
||||
|
||||
private final Int2ObjectOpenHashMap<TimingData> children = new LoadingIntMap<>(TimingData::new);
|
||||
|
||||
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 boolean timed;
|
||||
private boolean enabled;
|
||||
private TimingHandler parent;
|
||||
|
||||
TimingHandler(TimingIdentifier id) {
|
||||
if (id.name.startsWith("##")) {
|
||||
verbose = true;
|
||||
this.name = id.name.substring(3);
|
||||
} else {
|
||||
this.name = id.name;
|
||||
verbose = false;
|
||||
}
|
||||
|
||||
this.record = new TimingData(this.id);
|
||||
this.groupHandler = id.groupHandler;
|
||||
|
||||
TimingIdentifier.getGroup(id.group).handlers.add(this);
|
||||
checkEnabled();
|
||||
}
|
||||
|
||||
final void checkEnabled() {
|
||||
enabled = Timings.timingsEnabled && (!verbose || Timings.verboseEnabled);
|
||||
}
|
||||
|
||||
void processTick(boolean violated) {
|
||||
if (timingDepth != 0 || record.getCurTickCount() == 0) {
|
||||
timingDepth = 0;
|
||||
start = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
record.processTick(violated);
|
||||
for (TimingData handler : children.values()) {
|
||||
handler.processTick(violated);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
startTiming();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopTimingIfSync() {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing startTiming() {
|
||||
if (enabled && ++timingDepth == 1) {
|
||||
start = 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
if (enabled && timingDepth > 0) {
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void addDiff(long diff) {
|
||||
if (TimingsManager.CURRENT == this) {
|
||||
TimingsManager.CURRENT = parent;
|
||||
if (parent != null) {
|
||||
parent.children.get(id).add(diff);
|
||||
}
|
||||
}
|
||||
record.add(diff);
|
||||
if (!added) {
|
||||
added = true;
|
||||
timed = true;
|
||||
TimingsManager.HANDLERS.add(this);
|
||||
}
|
||||
if (groupHandler != null) {
|
||||
groupHandler.addDiff(diff);
|
||||
groupHandler.children.get(id).add(diff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this timer, setting all values to zero.
|
||||
*
|
||||
* @param full
|
||||
*/
|
||||
void reset(boolean full) {
|
||||
record.reset();
|
||||
if (full) {
|
||||
timed = false;
|
||||
}
|
||||
start = 0;
|
||||
timingDepth = 0;
|
||||
added = false;
|
||||
children.clear();
|
||||
checkEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimingHandler getTimingHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (this == o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is simply for the Closeable interface so it can be used with
|
||||
* try-with-resources ()
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
stopTimingIfSync();
|
||||
}
|
||||
|
||||
public boolean isSpecial() {
|
||||
return this == TimingsManager.FULL_SERVER_TICK || this == TimingsManager.TIMINGS_TICK;
|
||||
}
|
||||
|
||||
boolean isTimed() {
|
||||
return timed;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
TimingData[] cloneChildren() {
|
||||
final TimingData[] clonedChildren = new TimingData[children.size()];
|
||||
int i = 0;
|
||||
for (TimingData child : children.values()) {
|
||||
clonedChildren[i++] = child.clone();
|
||||
}
|
||||
return clonedChildren;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@ 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.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.google.common.collect.Queues;
|
||||
@@ -14,8 +17,15 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import co.aikar.timings.Timings;
|
||||
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||
import io.akarin.api.internal.Akari.TimingSignal;
|
||||
import io.akarin.api.internal.utils.ReentrantSpinningLock;
|
||||
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
|
||||
import io.akarin.api.internal.utils.thread.SuspendableThreadPoolExecutor;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public abstract class Akari {
|
||||
@@ -44,19 +54,59 @@ public abstract class Akari {
|
||||
}
|
||||
|
||||
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("Akarin Parallel Schedule Thread");
|
||||
thread.setName(StringUtils.replaceChars(threadName, "$", String.valueOf(threadNumber++)));
|
||||
thread.setPriority(AkarinGlobalConfig.primaryThreadPriority); // Fair
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A common tick pool
|
||||
*/
|
||||
public static final ExecutorCompletionService<?> STAGE_TICK = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor(new AssignableFactory()));
|
||||
public static class TimingSignal {
|
||||
public final World tickedWorld;
|
||||
public final boolean isEntities;
|
||||
|
||||
public TimingSignal(World world, boolean entities) {
|
||||
tickedWorld = world;
|
||||
isEntities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
public static SuspendableExecutorCompletionService<TimingSignal> STAGE_TICK;
|
||||
|
||||
static {
|
||||
resizeTickExecutors(3);
|
||||
}
|
||||
|
||||
public static void resizeTickExecutors(int worlds) {
|
||||
int parallelism;
|
||||
switch (AkarinGlobalConfig.parallelMode) {
|
||||
case -1:
|
||||
return;
|
||||
case 0:
|
||||
parallelism = 2;
|
||||
break;
|
||||
case 1:
|
||||
parallelism = worlds + 1;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
parallelism = worlds * 2;
|
||||
break;
|
||||
}
|
||||
STAGE_TICK = new SuspendableExecutorCompletionService<>(new SuspendableThreadPoolExecutor(parallelism, parallelism,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new AssignableFactory("Akarin Parallel Ticking Thread - $")));
|
||||
}
|
||||
|
||||
public static boolean isPrimaryThread() {
|
||||
return isPrimaryThread(true);
|
||||
@@ -64,7 +114,7 @@ public abstract class Akari {
|
||||
|
||||
public static boolean isPrimaryThread(boolean assign) {
|
||||
Thread current = Thread.currentThread();
|
||||
return current == MinecraftServer.getServer().primaryThread || (assign ? current instanceof AssignableThread : false);
|
||||
return current == MinecraftServer.getServer().primaryThread || (assign ? (current.getClass() == AssignableThread.class) : false);
|
||||
}
|
||||
|
||||
public static final String EMPTY_STRING = "";
|
||||
@@ -96,9 +146,7 @@ public abstract class Akari {
|
||||
*/
|
||||
public final static Timing worldTiming = getTiming("Akarin - Full World Tick");
|
||||
|
||||
public final static Timing entityCallbackTiming = getTiming("Akarin - Entity Callback");
|
||||
|
||||
public final static Timing callbackTiming = getTiming("Akarin - Callback");
|
||||
public final static Timing callbackTiming = getTiming("Akarin - Callback Queue");
|
||||
|
||||
private static Timing getTiming(String name) {
|
||||
try {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
|
||||
public interface IMixinChunk {
|
||||
AtomicInteger getPendingLightUpdates();
|
||||
|
||||
long getLightUpdateTime();
|
||||
|
||||
boolean areNeighborsLoaded();
|
||||
|
||||
@Nullable Chunk getNeighborChunk(int index);
|
||||
|
||||
CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type);
|
||||
|
||||
List<Chunk> getNeighbors();
|
||||
|
||||
void setNeighborChunk(int index, @Nullable Chunk chunk);
|
||||
|
||||
void setLightUpdateTime(long time);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
public interface IMixinLockProvider {
|
||||
public Object lock();
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
package io.akarin.api.internal.mixin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import java.util.Random;
|
||||
|
||||
public interface IMixinWorldServer {
|
||||
boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk chunk);
|
||||
|
||||
boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors);
|
||||
|
||||
ExecutorService getLightingExecutor();
|
||||
public Object lock();
|
||||
public Random rand();
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.akarin.api.internal.utils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ReentrantSpinningLock {
|
||||
/*
|
||||
* Impl Note:
|
||||
* A write lock can reentrant as a read lock, while a
|
||||
* read lock is not allowed to reentrant as a write lock.
|
||||
* READ LOCK IS UNTESTED, USE WITH CATION.
|
||||
*/
|
||||
private final AtomicBoolean writeLocked = new AtomicBoolean(false);
|
||||
|
||||
// --------- Thread local restricted fields ---------
|
||||
private long heldThreadId = 0;
|
||||
private int reentrantLocks = 0;
|
||||
|
||||
/**
|
||||
* Lock as a typical reentrant write lock
|
||||
*/
|
||||
public void lock() {
|
||||
long currentThreadId = Thread.currentThread().getId();
|
||||
if (heldThreadId == currentThreadId) {
|
||||
reentrantLocks++;
|
||||
} else {
|
||||
while (!writeLocked.compareAndSet(false, true)) ; // In case acquire one lock concurrently
|
||||
heldThreadId = currentThreadId;
|
||||
}
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
if (reentrantLocks == 0) {
|
||||
heldThreadId = 0;
|
||||
//if (readerThreads.get() == 0 || readerThreads.getAndDecrement() == 1) { // Micro-optimization: this saves one subtract
|
||||
writeLocked.set(false);
|
||||
//}
|
||||
} else {
|
||||
--reentrantLocks;
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicInteger readerThreads = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Lock as a typical reentrant read lock
|
||||
*/
|
||||
@Deprecated
|
||||
public void lockWeak() {
|
||||
long currentThreadId = Thread.currentThread().getId();
|
||||
if (heldThreadId == currentThreadId) {
|
||||
reentrantLocks++;
|
||||
} else {
|
||||
if (readerThreads.get() == 0) {
|
||||
while (!writeLocked.compareAndSet(false, true)) ; // Block future write lock
|
||||
}
|
||||
heldThreadId = currentThreadId;
|
||||
readerThreads.getAndIncrement(); // Micro-optimization: this saves one plus
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void unlockWeak() {
|
||||
if (reentrantLocks == 0) {
|
||||
heldThreadId = 0;
|
||||
writeLocked.set(false);
|
||||
} else {
|
||||
--reentrantLocks;
|
||||
}
|
||||
}
|
||||
|
||||
// --------- Wrappers to allow typical usages ---------
|
||||
private SpinningWriteLock wrappedWriteLock = new SpinningWriteLock();
|
||||
private SpinningReadLock wrappedReadLock = new SpinningReadLock();
|
||||
|
||||
public class SpinningWriteLock {
|
||||
public void lock() {
|
||||
lock();
|
||||
}
|
||||
public void unlock() {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public class SpinningReadLock {
|
||||
public void lock() {
|
||||
lockWeak();
|
||||
}
|
||||
public void unlock() {
|
||||
unlockWeak();
|
||||
}
|
||||
}
|
||||
|
||||
public SpinningWriteLock writeLock() {
|
||||
return wrappedWriteLock;
|
||||
}
|
||||
|
||||
public SpinningReadLock readLock() {
|
||||
return wrappedReadLock;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -160,16 +160,6 @@ public class AkarinGlobalConfig {
|
||||
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 long timeUpdateInterval;
|
||||
private static void timeUpdateInterval() {
|
||||
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
|
||||
@@ -185,16 +175,6 @@ public class AkarinGlobalConfig {
|
||||
keepAliveTimeout = getSeconds(getString("core.keep-alive-response-timeout", "30s")) * 1000;
|
||||
}
|
||||
|
||||
public static int asyncLightingThreads;
|
||||
private static void asyncLightingThreads() {
|
||||
asyncLightingThreads = getInt("core.async-lighting.executor-threads", 4);
|
||||
}
|
||||
|
||||
public static boolean asyncLightingWorkStealing;
|
||||
private static void asyncLightingWorkStealing() {
|
||||
asyncLightingWorkStealing = getBoolean("core.async-lighting.use-work-stealing", false);
|
||||
}
|
||||
|
||||
public static boolean throwOnAsyncCaught;
|
||||
private static void throwOnAsyncCaught() {
|
||||
throwOnAsyncCaught = getBoolean("core.thread-safe.async-catcher.throw-on-caught", true);
|
||||
@@ -211,52 +191,25 @@ public class AkarinGlobalConfig {
|
||||
}
|
||||
|
||||
public static String messageKick;
|
||||
private static void messageKick() {
|
||||
messageKick = getString("messages.disconnect.kick-player", "Kicked by an operator.");
|
||||
}
|
||||
|
||||
public static String messageBan;
|
||||
private static void messageBan() {
|
||||
messageBan = getString("messages.disconnect.ban-player-name", "You are banned from this server! %s %s");
|
||||
}
|
||||
|
||||
public static String messageBanReason;
|
||||
private static void messageBanReason() {
|
||||
messageBanReason = getString("messages.disconnect.ban-reason", "\nReason: ");
|
||||
}
|
||||
|
||||
public static String messageBanExpires;
|
||||
private static void messageBanExpires() {
|
||||
messageBanExpires = getString("messages.disconnect.ban-expires", "\nYour ban will be removed on ");
|
||||
}
|
||||
|
||||
public static String messageBanIp;
|
||||
private static void messageBanIp() {
|
||||
messageBanIp = getString("messages.disconnect.ban-player-ip", "Your IP address is banned from this server! %s %s");
|
||||
}
|
||||
|
||||
public static String messageDupLogin;
|
||||
private static void messageDupLogin() {
|
||||
messageDupLogin = getString("messages.disconnect.kick-player-duplicate-login", "You logged in from another location");
|
||||
}
|
||||
|
||||
public static String messageJoin;
|
||||
private static void messageJoin() {
|
||||
messageJoin = getString("messages.connect.player-join-server", "§e%s joined the game");
|
||||
}
|
||||
|
||||
public static String messageJoinRenamed;
|
||||
private static void messageJoinRenamed() {
|
||||
messageJoinRenamed = getString("messages.connect.renamed-player-join-server", "§e%s (formerly known as %s) joined the game");
|
||||
}
|
||||
|
||||
public static String messageKickKeepAlive;
|
||||
private static void messagekickKeepAlive() {
|
||||
messageKickKeepAlive = getString("messages.disconnect.kick-player-timeout-keep-alive", "Timed out");
|
||||
}
|
||||
|
||||
public static String messagePlayerQuit;
|
||||
private static void messagePlayerQuit() {
|
||||
private static void messagekickKeepAlive() {
|
||||
messageKick = getString("messages.disconnect.kick-player", "Kicked by an operator.");
|
||||
messageBan = getString("messages.disconnect.ban-player-name", "You are banned from this server! %s %s");
|
||||
messageBanReason = getString("messages.disconnect.ban-reason", "\nReason: ");
|
||||
messageBanExpires = getString("messages.disconnect.ban-expires", "\nYour ban will be removed on ");
|
||||
messageBanIp = getString("messages.disconnect.ban-player-ip", "Your IP address is banned from this server! %s %s");
|
||||
messageDupLogin = getString("messages.disconnect.kick-player-duplicate-login", "You logged in from another location");
|
||||
messageJoin = getString("messages.connect.player-join-server", "§e%s joined the game");
|
||||
messageJoinRenamed = getString("messages.connect.renamed-player-join-server", "§e%s (formerly known as %s) joined the game");
|
||||
messageKickKeepAlive = getString("messages.disconnect.kick-player-timeout-keep-alive", "Timed out");
|
||||
messagePlayerQuit = getString("messages.disconnect.player-quit-server", "§e%s left the game");
|
||||
}
|
||||
|
||||
@@ -294,4 +247,14 @@ public class AkarinGlobalConfig {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class AkarinSlackScheduler extends Thread {
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
|
||||
while (server.isRunning()) {
|
||||
long startProcessTiming = System.currentTimeMillis();
|
||||
// Send time updates to everyone, it will get the right time from the world the player is in.
|
||||
// Time update, from MinecraftServer#D
|
||||
if (++updateTime >= AkarinGlobalConfig.timeUpdateInterval) {
|
||||
@@ -97,10 +98,9 @@ public class AkarinSlackScheduler extends Thread {
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Akari.logger.warn("Slack scheduler thread was interrupted unexpectly!");
|
||||
ex.printStackTrace();
|
||||
Thread.sleep(100 - (System.currentTimeMillis() - startProcessTiming));
|
||||
} catch (InterruptedException interrupted) {
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -23,6 +22,10 @@ import net.minecraft.server.MinecraftServer;
|
||||
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,13 +41,23 @@ public abstract 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
|
||||
// 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();
|
||||
log.log(Level.SEVERE, "Server has stopped responding!");
|
||||
log.log(Level.SEVERE, "Please report this to https://github.com/Akarin-project/Akarin/issues");
|
||||
// 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());
|
||||
log.log(Level.SEVERE, "Akarin version: " + Bukkit.getServer().getVersion()); // Akarin
|
||||
//
|
||||
if (net.minecraft.server.World.haveWeSilencedAPhysicsCrash) {
|
||||
log.log(Level.SEVERE, "------------------------------");
|
||||
@@ -52,34 +65,49 @@ public abstract class Watchcat extends Thread {
|
||||
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) {
|
||||
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, CraftServer.excessiveVelEx.getMessage());
|
||||
for (StackTraceElement stack : CraftServer.excessiveVelEx.getStackTrace()) {
|
||||
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, "------------------------------");
|
||||
//
|
||||
// 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(); // GC Inlined
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.destroystokyo.paper.PaperConfig;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.FileIOThread;
|
||||
import net.minecraft.server.IAsyncChunkSaver;
|
||||
|
||||
@Mixin(value = FileIOThread.class, remap = false)
|
||||
public abstract class MixinFileIOThread {
|
||||
private final Executor executor = Executors.newFixedThreadPool(AkarinGlobalConfig.fileIOThreads, new ThreadFactoryBuilder().setNameFormat("Akarin File IO Thread - %1$d").setPriority(1).build());
|
||||
private final AtomicInteger queuedChunkCounter = new AtomicInteger(0);
|
||||
|
||||
@Shadow(aliases = "e") private volatile boolean isAwaitFinish;
|
||||
|
||||
@Overwrite // OBFHELPER: saveChunk
|
||||
public void a(IAsyncChunkSaver iasyncchunksaver) {
|
||||
queuedChunkCounter.incrementAndGet();
|
||||
executor.execute(() -> writeChunk(iasyncchunksaver));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a chunk, re-add to the queue if unsuccessful
|
||||
*/
|
||||
private void writeChunk(IAsyncChunkSaver iasyncchunksaver) {
|
||||
if (!iasyncchunksaver.a()) { // PAIL: WriteNextIO() -> Returns if the write was unsuccessful
|
||||
queuedChunkCounter.decrementAndGet();
|
||||
|
||||
if (PaperConfig.enableFileIOThreadSleep) {
|
||||
try {
|
||||
Thread.sleep(isAwaitFinish ? 0L : 2L);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeChunk(iasyncchunksaver);
|
||||
}
|
||||
}
|
||||
|
||||
@Overwrite // OBFHELPER: waitForFinish
|
||||
public void b() throws InterruptedException {
|
||||
isAwaitFinish = true;
|
||||
while (queuedChunkCounter.get() != 0) Thread.sleep(9L);
|
||||
isAwaitFinish = false;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +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 abstract class MixinItemMonsterEgg {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||
@@ -23,7 +26,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import co.aikar.timings.MinecraftTimings;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||
import io.akarin.api.internal.mixin.IMixinLockProvider;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import io.akarin.server.core.AkarinSlackScheduler;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
@@ -42,6 +45,8 @@ import net.minecraft.server.WorldServer;
|
||||
@Mixin(value = MinecraftServer.class, remap = false)
|
||||
public abstract class MixinMinecraftServer {
|
||||
@Shadow @Final public Thread primaryThread;
|
||||
private boolean tickedPrimaryEntities;
|
||||
private int cachedWorldSize;
|
||||
|
||||
@Overwrite
|
||||
public String getServerModName() {
|
||||
@@ -56,6 +61,7 @@ public abstract class MixinMinecraftServer {
|
||||
private void prerun(CallbackInfo info) {
|
||||
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
||||
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
||||
Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
@@ -109,7 +115,7 @@ public abstract class MixinMinecraftServer {
|
||||
|
||||
@Overwrite
|
||||
protected void l() throws InterruptedException {
|
||||
ExecutorCompletionService<?> executor = new ExecutorCompletionService<>(Executors.newFixedThreadPool(worlds.size(), new AssignableFactory()));
|
||||
ExecutorCompletionService<?> executor = new ExecutorCompletionService<>(Executors.newFixedThreadPool(worlds.size(), new AssignableFactory("Akarin Parallel Terrain Generation Thread - $")));
|
||||
|
||||
for (int index = 0; index < worlds.size(); index++) {
|
||||
WorldServer world = this.worlds.get(index);
|
||||
@@ -148,7 +154,11 @@ public abstract class MixinMinecraftServer {
|
||||
|
||||
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 {
|
||||
@@ -164,7 +174,9 @@ public abstract class MixinMinecraftServer {
|
||||
|
||||
private void tickWorld(WorldServer world) {
|
||||
try {
|
||||
world.timings.doTick.startTiming();
|
||||
world.doTick();
|
||||
world.timings.doTick.stopTiming();
|
||||
} catch (Throwable throwable) {
|
||||
CrashReport crashreport;
|
||||
try {
|
||||
@@ -178,8 +190,12 @@ public abstract class MixinMinecraftServer {
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void D() throws InterruptedException {
|
||||
public void D() throws InterruptedException, ExecutionException, CancellationException {
|
||||
Runnable runnable;
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.stopTiming();
|
||||
|
||||
MinecraftTimings.bukkitSchedulerTimer.startTiming();
|
||||
this.server.getScheduler().mainThreadHeartbeat(this.ticks);
|
||||
MinecraftTimings.bukkitSchedulerTimer.stopTiming();
|
||||
@@ -200,54 +216,92 @@ public abstract class MixinMinecraftServer {
|
||||
ChunkIOExecutor.tick();
|
||||
MinecraftTimings.chunkIOTickTimer.stopTiming();
|
||||
|
||||
Akari.worldTiming.startTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
world.timings.tickEntities.startTiming();
|
||||
world.timings.doTick.startTiming();
|
||||
}
|
||||
}
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
if (cachedWorldSize != worlds.size()) Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||
switch (AkarinGlobalConfig.parallelMode) {
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
// Never tick one world concurrently!
|
||||
for (int i = 1; i <= worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i < worlds.size() ? i : 0);
|
||||
synchronized (((IMixinLockProvider) world).lock()) {
|
||||
for (int i = 0; i < cachedWorldSize; i++) {
|
||||
// Impl Note:
|
||||
// Entities ticking: index 1 -> ... -> 0 (parallel)
|
||||
// World ticking: index 0 -> ... (parallel)
|
||||
int interlace = i + 1;
|
||||
WorldServer entityWorld = worlds.get(interlace < cachedWorldSize ? interlace : 0);
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinWorldServer) entityWorld).lock()) {
|
||||
tickEntities(entityWorld);
|
||||
}
|
||||
}, null/*new TimingSignal(entityWorld, true)*/);
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode != 1) {
|
||||
int fi = i;
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
WorldServer world = worlds.get(fi);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (AkarinGlobalConfig.parallelMode == 1)
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 0; i < cachedWorldSize; i++) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize + 1 : cachedWorldSize * 2); i --> 0 ;) {
|
||||
Akari.STAGE_TICK.take();
|
||||
}
|
||||
|
||||
/* for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize : cachedWorldSize * 2); i --> 0 ;) {
|
||||
long startTiming = System.nanoTime();
|
||||
TimingSignal signal = Akari.STAGE_TICK.take().get();
|
||||
IMixinTimingHandler timing = (IMixinTimingHandler) (signal.isEntities ? signal.tickedWorld.timings.tickEntities : signal.tickedWorld.timings.doTick);
|
||||
timing.stopTiming(startTiming); // The overlap will be ignored
|
||||
} */
|
||||
|
||||
break;
|
||||
case 0:
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 1; i <= cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i < cachedWorldSize ? i : 0);
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickEntities(world);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (((IMixinLockProvider) world).lock()) {
|
||||
synchronized (((IMixinWorldServer) world).lock()) {
|
||||
tickWorld(world);
|
||||
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
Akari.entityCallbackTiming.startTiming();
|
||||
Akari.STAGE_TICK.take();
|
||||
Akari.entityCallbackTiming.stopTiming();
|
||||
|
||||
Akari.worldTiming.stopTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
Akari.STAGE_TICK.take();
|
||||
break;
|
||||
case -1:
|
||||
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
world.timings.tickEntities.stopTiming();
|
||||
world.timings.doTick.stopTiming();
|
||||
tickWorld(world);
|
||||
tickEntities(world);
|
||||
}
|
||||
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);
|
||||
tickUnsafeSync(world);
|
||||
}
|
||||
|
||||
MinecraftTimings.connectionTimer.startTiming();
|
||||
serverConnection().c();
|
||||
MinecraftTimings.connectionTimer.stopTiming();
|
||||
@@ -266,12 +320,4 @@ public abstract class MixinMinecraftServer {
|
||||
}
|
||||
MinecraftTimings.tickablesTimer.stopTiming();
|
||||
}
|
||||
|
||||
public void tickUnsafeSync(WorldServer world) {
|
||||
world.timings.doChunkMap.startTiming();
|
||||
world.manager.flush();
|
||||
world.timings.doChunkMap.stopTiming();
|
||||
|
||||
world.getTracker().updatePlayers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -7,13 +9,8 @@ import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.Akari.AssignableThread;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@@ -21,26 +18,18 @@ import net.minecraft.server.MinecraftServer;
|
||||
public abstract class MixinTimingHandler {
|
||||
@Shadow @Final String name;
|
||||
@Shadow private boolean enabled;
|
||||
@Shadow private volatile long start;
|
||||
@Shadow private volatile int timingDepth;
|
||||
@Shadow private long start;
|
||||
@Shadow private int timingDepth;
|
||||
|
||||
@Shadow abstract void addDiff(long diff);
|
||||
@Shadow public abstract Timing startTiming();
|
||||
|
||||
@Overwrite
|
||||
public Timing startTimingIfSync() {
|
||||
if (Akari.isPrimaryThread(false)) {
|
||||
startTiming();
|
||||
}
|
||||
return (Timing) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Inject(method = "startTiming", at = @At("HEAD"), cancellable = true)
|
||||
public void onStartTiming(CallbackInfoReturnable ci) {
|
||||
if (!Akari.isPrimaryThread(false)) ci.setReturnValue(this); // Avoid modify any field
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void stopTimingIfSync() {
|
||||
if (Akari.isPrimaryThread(false)) {
|
||||
@@ -53,20 +42,22 @@ public abstract class MixinTimingHandler {
|
||||
stopTiming(false);
|
||||
}
|
||||
|
||||
public void stopTiming(boolean alreadySync) {
|
||||
Thread curThread = Thread.currentThread();
|
||||
if (!enabled || curThread instanceof AssignableThread) return;
|
||||
if (!alreadySync && curThread != MinecraftServer.getServer().primaryThread) {
|
||||
if (AkarinGlobalConfig.silentAsyncTimings) return;
|
||||
|
||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||
Thread.dumpStack();
|
||||
public void stopTiming(long start) {
|
||||
if (enabled) addDiff(System.nanoTime() - start);
|
||||
}
|
||||
|
||||
// Main thread ensured
|
||||
if (--timingDepth == 0 && start != 0) {
|
||||
public void stopTiming(boolean alreadySync) {
|
||||
if (!enabled || --timingDepth != 0 || start == 0) return;
|
||||
if (!alreadySync) {
|
||||
Thread curThread = Thread.currentThread();
|
||||
if (curThread != MinecraftServer.getServer().primaryThread) {
|
||||
start = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Safety ensured
|
||||
addDiff(System.nanoTime() - start);
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,58 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.defaults.VersionCommand;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MCUtil;
|
||||
|
||||
@Mixin(value = VersionCommand.class, remap = false)
|
||||
public abstract class MixinVersionCommand {
|
||||
@Shadow private static int getFromRepo(String repo, String hash) { return 0; }
|
||||
@Overwrite
|
||||
private static int getFromRepo(String repo, String hash) {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/ver/1.12.2..." + hash).openConnection();
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit
|
||||
try (
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))
|
||||
) {
|
||||
JSONObject obj = (JSONObject) new JSONParser().parse(reader);
|
||||
String status = (String) obj.get("status");
|
||||
switch (status) {
|
||||
case "identical":
|
||||
return 0;
|
||||
case "behind":
|
||||
return ((Number) obj.get("behind_by")).intValue();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} catch (ParseException | NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match current version with repository and calculate the distance
|
||||
|
||||
@@ -16,9 +16,9 @@ public abstract class MixinWorldManager {
|
||||
|
||||
@Overwrite
|
||||
public void a(Entity entity) {
|
||||
this.world.getTracker().entriesLock.lock(); // Akarin
|
||||
this.world.getTracker().entriesLock.writeLock().lock(); // Akarin
|
||||
this.world.getTracker().track(entity);
|
||||
this.world.getTracker().entriesLock.unlock(); // Akarin
|
||||
this.world.getTracker().entriesLock.writeLock().unlock(); // Akarin
|
||||
|
||||
if (entity instanceof EntityPlayer) {
|
||||
this.world.worldProvider.a((EntityPlayer) entity);
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
package io.akarin.server.mixin.core;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import java.util.Random;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinLockProvider;
|
||||
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 IMixinLockProvider {
|
||||
@Redirect(method = "doTick()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "net/minecraft/server/PlayerChunkMap.flush()V"
|
||||
))
|
||||
public void onFlush() {} // Migrated to main thread
|
||||
|
||||
public abstract class MixinWorldServer implements IMixinWorldServer {
|
||||
private final Object tickLock = new Object();
|
||||
|
||||
@Override
|
||||
public Object lock() {
|
||||
return tickLock;
|
||||
}
|
||||
|
||||
private final Random sharedRandom = new io.akarin.api.internal.utils.random.LightRandom() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private boolean locked = false;
|
||||
@Override
|
||||
public synchronized void setSeed(long seed) {
|
||||
if (locked) {
|
||||
LogManager.getLogger().error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable());
|
||||
} else {
|
||||
super.setSeed(seed);
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Random rand() {
|
||||
return sharedRandom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.cps;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinChunk;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
private Chunk[] neighborChunks = new Chunk[4];
|
||||
private static final EnumDirection[] CARDINAL_DIRECTIONS = new EnumDirection[] {EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.EAST, EnumDirection.WEST};
|
||||
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
|
||||
@Override
|
||||
public Chunk getNeighborChunk(int index) {
|
||||
return this.neighborChunks[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeighborChunk(int index, @Nullable Chunk chunk) {
|
||||
this.neighborChunks[index] = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Chunk> getNeighbors() {
|
||||
List<Chunk> neighborList = Lists.newArrayList();
|
||||
for (Chunk neighbor : this.neighborChunks) {
|
||||
if (neighbor != null) {
|
||||
neighborList.add(neighbor);
|
||||
}
|
||||
}
|
||||
return neighborList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areNeighborsLoaded() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (this.neighborChunks[i] == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int directionToIndex(EnumDirection direction) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
return 0;
|
||||
case SOUTH:
|
||||
return 1;
|
||||
case EAST:
|
||||
return 2;
|
||||
case WEST:
|
||||
return 3;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected direction");
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "addEntities", at = @At("RETURN"))
|
||||
public void onLoadReturn(CallbackInfo ci) {
|
||||
BlockPosition origin = new BlockPosition(locX, 0, locZ);
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
BlockPosition shift = origin.shift(direction);
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), shift.getX(), shift.getZ());
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, neighbor);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, (Chunk) (Object) this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "removeEntities", at = @At("RETURN"))
|
||||
public void onUnload(CallbackInfo ci) {
|
||||
BlockPosition origin = new BlockPosition(locX, 0, locZ);
|
||||
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
|
||||
BlockPosition shift = origin.shift(direction);
|
||||
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), shift.getX(), shift.getZ());
|
||||
if (neighbor != null) {
|
||||
int neighborIndex = directionToIndex(direction);
|
||||
int oppositeNeighborIndex = directionToIndex(direction.opposite());
|
||||
this.setNeighborChunk(neighborIndex, null);
|
||||
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public abstract class MixinChunkProviderServer {
|
||||
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
||||
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
|
||||
activityAccountant.startActivity(0.5);
|
||||
|
||||
ObjectIterator<Entry<Chunk>> it = chunks.long2ObjectEntrySet().fastIterator();
|
||||
int remainingChunks = chunks.size();
|
||||
int targetSize = Math.min(remainingChunks - 100, (int) (remainingChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
||||
@@ -47,23 +48,19 @@ public abstract class MixinChunkProviderServer {
|
||||
while (it.hasNext()) {
|
||||
Entry<Chunk> entry = it.next();
|
||||
Chunk chunk = entry.getValue();
|
||||
if (chunk == null) continue;
|
||||
|
||||
if (chunk.isUnloading()) {
|
||||
if (chunk != null && chunk.isUnloading()) {
|
||||
if (chunk.scheduledForUnload != null) {
|
||||
if (now - chunk.scheduledForUnload > unloadAfter) {
|
||||
chunk.scheduledForUnload = null;
|
||||
} else continue;
|
||||
}
|
||||
|
||||
if (!unloadChunk(chunk, true)) { // Event cancelled
|
||||
// If a plugin cancelled it, we shouldn't trying unload it for a while
|
||||
chunk.setShouldUnload(false);
|
||||
continue;
|
||||
if (now - chunk.scheduledForUnload <= unloadAfter) continue;
|
||||
}
|
||||
|
||||
if (unloadChunk(chunk, true)) {
|
||||
it.remove();
|
||||
if (--remainingChunks <= targetSize || activityAccountant.activityTimeIsExhausted()) break; // more slack since the target size not work as intended
|
||||
}
|
||||
chunk.setShouldUnload(false);
|
||||
chunk.scheduledForUnload = null;
|
||||
|
||||
if (--remainingChunks <= targetSize && activityAccountant.activityTimeIsExhausted()) break;
|
||||
}
|
||||
}
|
||||
activityAccountant.endActivity();
|
||||
|
||||
@@ -1,671 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.mixin.IMixinChunk;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Blocks;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.ChunkSection;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.TileEntity;
|
||||
import net.minecraft.server.World;
|
||||
import net.minecraft.server.BlockPosition.MutableBlockPosition;
|
||||
|
||||
@Mixin(value = Chunk.class, remap = false, priority = 1001)
|
||||
public abstract class MixinChunk implements IMixinChunk {
|
||||
|
||||
// Keeps track of block positions in this chunk currently queued for sky light update
|
||||
private CopyOnWriteArrayList<Short> queuedSkyLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
// Keeps track of block positions in this chunk currently queued for block light update
|
||||
private CopyOnWriteArrayList<Short> queuedBlockLightingUpdates = new CopyOnWriteArrayList<>();
|
||||
private AtomicInteger pendingLightUpdates = new AtomicInteger();
|
||||
private long lightUpdateTime;
|
||||
private static ExecutorService lightExecutorService;
|
||||
|
||||
@Shadow(aliases = "m") private boolean isGapLightingUpdated;
|
||||
@Shadow(aliases = "r") private boolean ticked;
|
||||
@Shadow @Final private ChunkSection[] sections;
|
||||
@Shadow @Final public int locX;
|
||||
@Shadow @Final public int locZ;
|
||||
@Shadow @Final public World world;
|
||||
@Shadow @Final public int[] heightMap;
|
||||
/** Which columns need their skylightMaps updated. */
|
||||
@Shadow(aliases = "i") @Final private boolean[] updateSkylightColumns;
|
||||
/** Queue containing the BlockPosition of tile entities queued for creation */
|
||||
@Shadow(aliases = "y") @Final private ConcurrentLinkedQueue<BlockPosition> tileEntityPosQueue;
|
||||
/** Boolean value indicating if the terrain is populated. */
|
||||
@Shadow(aliases = "done") private boolean isTerrainPopulated;
|
||||
@Shadow(aliases = "lit") private boolean isLightPopulated;
|
||||
/** Lowest value in the heightmap. */
|
||||
@Shadow(aliases = "v") private int heightMapMinimum;
|
||||
|
||||
@Shadow(aliases = "b") public abstract int getHeightValue(int x, int z);
|
||||
@Shadow(aliases = "g") @Nullable public abstract TileEntity createNewTileEntity(BlockPosition pos);
|
||||
@Shadow(aliases = "a") @Nullable public abstract TileEntity getTileEntity(BlockPosition pos, Chunk.EnumTileEntityState state);
|
||||
@Shadow @Final public abstract IBlockData getBlockData(BlockPosition pos);
|
||||
@Shadow @Final public abstract IBlockData getBlockData(int x, int y, int z);
|
||||
@Shadow public abstract boolean isUnloading();
|
||||
/** Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary */
|
||||
@Shadow(aliases = "b") public abstract void checkSkylightNeighborHeight(int x, int z, int maxValue);
|
||||
@Shadow(aliases = "a") public abstract void updateSkylightNeighborHeight(int x, int z, int startY, int endY);
|
||||
@Shadow(aliases = "z") public abstract void setSkylightUpdated();
|
||||
@Shadow(aliases = "g") public abstract int getTopFilledSegment();
|
||||
@Shadow public abstract void markDirty();
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
public void onConstruct(World worldIn, int x, int z, CallbackInfo ci) {
|
||||
lightExecutorService = ((IMixinWorldServer) worldIn).getLightingExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtomicInteger getPendingLightUpdates() {
|
||||
return this.pendingLightUpdates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLightUpdateTime() {
|
||||
return this.lightUpdateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightUpdateTime(long time) {
|
||||
this.lightUpdateTime = time;
|
||||
}
|
||||
|
||||
@Inject(method = "b(Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onTickHead(boolean skipRecheckGaps, CallbackInfo ci) {
|
||||
final List<Chunk> neighbors = this.getSurroundingChunks();
|
||||
if (this.isGapLightingUpdated && this.world.worldProvider.m() && !skipRecheckGaps && !neighbors.isEmpty()) { // OBFHELPER: hasSkyLight
|
||||
lightExecutorService.execute(() -> {
|
||||
this.recheckGapsAsync(neighbors);
|
||||
});
|
||||
this.isGapLightingUpdated = false;
|
||||
}
|
||||
|
||||
this.ticked = true;
|
||||
|
||||
if ((true || !this.isLightPopulated) && this.isTerrainPopulated && !neighbors.isEmpty()) {
|
||||
lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighbors);
|
||||
});
|
||||
// set to true to avoid requeuing the same task when not finished
|
||||
this.isLightPopulated = true;
|
||||
}
|
||||
|
||||
while (!this.tileEntityPosQueue.isEmpty()) {
|
||||
BlockPosition blockpos = this.tileEntityPosQueue.poll();
|
||||
|
||||
if (this.getTileEntity(blockpos, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockpos).getBlock().isTileEntity()) { // OBFHELPER: getTileEntity
|
||||
TileEntity tileentity = this.createNewTileEntity(blockpos);
|
||||
this.world.setTileEntity(blockpos, tileentity);
|
||||
this.world.b(blockpos, blockpos); // OBFHELPER: markBlockRangeForRenderUpdate
|
||||
}
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Redirect(method = "b(III)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getHighestBlockYAt(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/BlockPosition;"))
|
||||
private BlockPosition onCheckSkylightGetHeight(World world, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return BlockPosition.ZERO;
|
||||
}
|
||||
|
||||
return new BlockPosition(pos.getX(), chunk.b(pos.getX() & 15, pos.getZ() & 15), pos.getZ()); // OBFHELPER: getHeightValue
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.areChunksLoaded(Lnet/minecraft/server/BlockPosition;I)Z"))
|
||||
private boolean onAreaLoadedSkyLightNeighbor(World world, BlockPosition pos, int radius) {
|
||||
return this.isAreaLoaded();
|
||||
}
|
||||
|
||||
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.c(Lnet/minecraft/server/EnumSkyBlock;Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightForSkylightNeighbor(World world, EnumSkyBlock enumSkyBlock, BlockPosition pos) {
|
||||
return this.checkWorldLightFor(enumSkyBlock, pos);
|
||||
}
|
||||
|
||||
@Inject(method = "h(Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onRecheckGaps(CallbackInfo ci) {
|
||||
if (this.world.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUnloading()) {
|
||||
return;
|
||||
}
|
||||
final List<Chunk> neighborChunks = this.getSurroundingChunks();
|
||||
if (neighborChunks.isEmpty()) {
|
||||
this.isGapLightingUpdated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Akari.isPrimaryThread()) {
|
||||
try {
|
||||
lightExecutorService.execute(() -> {
|
||||
this.recheckGapsAsync(neighborChunks);
|
||||
});
|
||||
} catch (RejectedExecutionException ex) {
|
||||
// This could happen if ServerHangWatchdog kills the server
|
||||
// between the start of the method and the execute() call.
|
||||
if (!this.world.getMinecraftServer().isStopped() && !lightExecutorService.isShutdown()) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.recheckGapsAsync(neighborChunks);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechecks chunk gaps async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void recheckGapsAsync(List<Chunk> neighbors) {
|
||||
this.isLightPopulated = false;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (this.updateSkylightColumns[i + j * 16]) {
|
||||
this.updateSkylightColumns[i + j * 16] = false;
|
||||
int k = this.getHeightValue(i, j);
|
||||
int l = this.locX * 16 + i;
|
||||
int i1 = this.locZ * 16 + j;
|
||||
int j1 = Integer.MAX_VALUE;
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
final Chunk chunk = this.getLightChunk((l + enumfacing.getAdjacentX()) >> 4, (i1 + enumfacing.getAdjacentZ()) >> 4, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
continue;
|
||||
}
|
||||
j1 = Math.min(j1, chunk.w()); // OBFHELPER: getLowestHeight
|
||||
}
|
||||
|
||||
this.checkSkylightNeighborHeight(l, i1, j1);
|
||||
|
||||
for (EnumDirection enumfacing1 : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.checkSkylightNeighborHeight(l + enumfacing1.getAdjacentX(), i1 + enumfacing1.getAdjacentZ(), k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isGapLightingUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getType(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/IBlockData;"))
|
||||
private IBlockData onRelightChecksGetBlockData(World world, BlockPosition pos) {
|
||||
Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), pos.getX() >> 4, pos.getZ() >> 4);
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) chunk;
|
||||
if (chunk == null || chunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return Blocks.AIR.getBlockData();
|
||||
}
|
||||
|
||||
return chunk.getBlockData(pos);
|
||||
}
|
||||
|
||||
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onRelightChecksCheckLight(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
// Avoids grabbing chunk async during light check
|
||||
@Redirect(method = "e(II)Z", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
|
||||
private boolean onCheckLightWorld(World world, BlockPosition pos) {
|
||||
return this.checkWorldLight(pos);
|
||||
}
|
||||
|
||||
@Inject(method = "o()V", at = @At("HEAD"), cancellable = true)
|
||||
private void checkLightHead(CallbackInfo ci) {
|
||||
if (this.world.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUnloading()) {
|
||||
return;
|
||||
}
|
||||
final List<Chunk> neighborChunks = this.getSurroundingChunks();
|
||||
if (neighborChunks.isEmpty()) {
|
||||
this.isLightPopulated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Akari.isPrimaryThread()) {
|
||||
try {
|
||||
lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
});
|
||||
} catch (RejectedExecutionException ex) {
|
||||
// This could happen if ServerHangWatchdog kills the server
|
||||
// between the start of the method and the execute() call.
|
||||
if (!this.world.getMinecraftServer().isStopped() && !lightExecutorService.isShutdown()) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.checkLightAsync(neighborChunks);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
*/
|
||||
private void checkLightAsync(List<Chunk> neighbors) {
|
||||
this.isTerrainPopulated = true;
|
||||
this.isLightPopulated = true;
|
||||
BlockPosition blockpos = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
|
||||
|
||||
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
|
||||
CHECK_LIGHT:
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (!this.checkLightAsync(i, j, neighbors)) {
|
||||
this.isLightPopulated = false;
|
||||
break CHECK_LIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isLightPopulated) {
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
int k = enumfacing.c() == EnumDirection.EnumAxisDirection.POSITIVE ? 16 : 1; // OBFHELPER: getAxisDirection
|
||||
final BlockPosition pos = blockpos.shift(enumfacing, k);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
chunk.a(enumfacing.opposite()); // OBFHELPER: checkLightSide
|
||||
}
|
||||
|
||||
this.setSkylightUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks light async.
|
||||
*
|
||||
* @param x The x position of chunk
|
||||
* @param z The z position of chunk
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkLightAsync(int x, int z, List<Chunk> neighbors) {
|
||||
int i = this.getTopFilledSegment();
|
||||
boolean flag = false;
|
||||
boolean flag1 = false;
|
||||
MutableBlockPosition blockpos$mutableblockpos = new MutableBlockPosition((this.locX << 4) + x, 0, (this.locZ << 4) + z);
|
||||
|
||||
for (int j = i + 16 - 1; j > this.world.getSeaLevel() || j > 0 && !flag1; --j) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), j, blockpos$mutableblockpos.getZ());
|
||||
int k = this.getBlockData(blockpos$mutableblockpos).c(); // OBFHELPER: getLightOpacity
|
||||
|
||||
if (k == 255 && blockpos$mutableblockpos.getY() < this.world.getSeaLevel()) {
|
||||
flag1 = true;
|
||||
}
|
||||
|
||||
if (!flag && k > 0) {
|
||||
flag = true;
|
||||
} else if (flag && k == 0 && !this.checkWorldLight(blockpos$mutableblockpos, neighbors)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int l = blockpos$mutableblockpos.getY(); l > 0; --l) {
|
||||
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), l, blockpos$mutableblockpos.getZ());
|
||||
|
||||
if (this.getBlockData(blockpos$mutableblockpos).d() > 0) { // getLightValue
|
||||
this.checkWorldLight(blockpos$mutableblockpos, neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe method to retrieve a chunk during async light updates.
|
||||
*
|
||||
* @param chunkX The x position of chunk.
|
||||
* @param chunkZ The z position of chunk.
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return The chunk if available, null if not
|
||||
*/
|
||||
private Chunk getLightChunk(int chunkX, int chunkZ, List<Chunk> neighbors) {
|
||||
final Chunk currentChunk = (Chunk) (Object) this;
|
||||
if (currentChunk.a(chunkX, chunkZ)) { // OBFHELPER: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
if (neighbors == null) {
|
||||
neighbors = this.getSurroundingChunks();
|
||||
if (neighbors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(chunkX, chunkZ)) { // OBFHELPER: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if surrounding chunks are loaded thread-safe.
|
||||
*
|
||||
* @return True if surrounded chunks are loaded, false if not
|
||||
*/
|
||||
private boolean isAreaLoaded() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets surrounding chunks thread-safe.
|
||||
*
|
||||
* @return The list of surrounding chunks, empty list if not loaded
|
||||
*/
|
||||
private List<Chunk> getSurroundingChunks() {
|
||||
if (!this.areNeighborsLoaded()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// add diagonal chunks
|
||||
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
if (southEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
if (southWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
if (northEastChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (northWestChunk == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Chunk> chunkList = this.getNeighbors();
|
||||
chunkList.add(southEastChunk);
|
||||
chunkList.add(southWestChunk);
|
||||
chunkList.add(northEastChunk);
|
||||
chunkList.add(northWestChunk);
|
||||
return chunkList;
|
||||
}
|
||||
|
||||
@Inject(method = "c(III)V", at = @At("HEAD"), cancellable = true)
|
||||
private void onRelightBlock(int x, int y, int z, CallbackInfo ci) {
|
||||
lightExecutorService.execute(() -> {
|
||||
this.relightBlockAsync(x, y, z);
|
||||
});
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relight's a block async.
|
||||
*
|
||||
* @param x The x position
|
||||
* @param y The y position
|
||||
* @param z The z position
|
||||
*/
|
||||
private void relightBlockAsync(int x, int y, int z) {
|
||||
int i = this.heightMap[z << 4 | x] & 255;
|
||||
int j = i;
|
||||
|
||||
if (y > i) {
|
||||
j = y;
|
||||
}
|
||||
|
||||
while (j > 0 && this.getBlockData(x, j - 1, z).c() == 0) { // OBFHELPER: getLightOpacity
|
||||
--j;
|
||||
}
|
||||
|
||||
if (j != i) {
|
||||
this.markBlocksDirtyVerticalAsync(x + this.locX * 16, z + this.locZ * 16, j, i);
|
||||
this.heightMap[z << 4 | x] = j;
|
||||
int k = this.locX * 16 + x;
|
||||
int l = this.locZ * 16 + z;
|
||||
|
||||
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
|
||||
if (j < i) {
|
||||
for (int j1 = j; j1 < i; ++j1) {
|
||||
ChunkSection extendedblockstorage2 = this.sections[j1 >> 4];
|
||||
|
||||
if (extendedblockstorage2 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage2.a(x, j1 & 15, z, 15); // OBFHELPER: setSkyLight
|
||||
// this.world.m(new BlockPosition((this.locX << 4) + x, j1, (this.locZ << 4) + z)); // OBFHELPER: notifyLightSet - client side
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i1 = i; i1 < j; ++i1) {
|
||||
ChunkSection extendedblockstorage = this.sections[i1 >> 4];
|
||||
|
||||
if (extendedblockstorage != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage.a(x, i1 & 15, z, 0); // OBFHELPER: setSkyLight
|
||||
// this.world.m(new BlockPosition((this.locX << 4) + x, i1, (this.locZ << 4) + z)); // OBFHELPER: notifyLightSet - client side
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int k1 = 15;
|
||||
|
||||
while (j > 0 && k1 > 0) {
|
||||
--j;
|
||||
int i2 = this.getBlockData(x, j, z).c();
|
||||
|
||||
if (i2 == 0) {
|
||||
i2 = 1;
|
||||
}
|
||||
|
||||
k1 -= i2;
|
||||
|
||||
if (k1 < 0) {
|
||||
k1 = 0;
|
||||
}
|
||||
|
||||
ChunkSection extendedblockstorage1 = this.sections[j >> 4];
|
||||
|
||||
if (extendedblockstorage1 != Chunk.EMPTY_CHUNK_SECTION) {
|
||||
extendedblockstorage1.a(x, j & 15, z, k1); // OBFHELPER: setSkyLight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int l1 = this.heightMap[z << 4 | x];
|
||||
int j2 = i;
|
||||
int k2 = l1;
|
||||
|
||||
if (l1 < i) {
|
||||
j2 = l1;
|
||||
k2 = i;
|
||||
}
|
||||
|
||||
if (l1 < this.heightMapMinimum) {
|
||||
this.heightMapMinimum = l1;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
|
||||
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
|
||||
this.updateSkylightNeighborHeight(k + enumfacing.getAdjacentX(), l + enumfacing.getAdjacentZ(), j2, k2); // OBFHELPER: updateSkylightNeighborHeight
|
||||
}
|
||||
|
||||
this.updateSkylightNeighborHeight(k, l, j2, k2);
|
||||
}
|
||||
|
||||
this.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a vertical line of blocks as dirty async.
|
||||
* Instead of calling world directly, we pass chunk safely for async light method.
|
||||
*
|
||||
* @param x1
|
||||
* @param z1
|
||||
* @param x2
|
||||
* @param z2
|
||||
*/
|
||||
private void markBlocksDirtyVerticalAsync(int x1, int z1, int x2, int z2) {
|
||||
if (x2 > z2) {
|
||||
int i = z2;
|
||||
z2 = x2;
|
||||
x2 = i;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
|
||||
for (int j = x2; j <= z2; ++j) {
|
||||
final BlockPosition pos = new BlockPosition(x1, j, z1);
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, new BlockPosition(x1, j, z1), chunk);
|
||||
}
|
||||
}
|
||||
|
||||
this.world.b(x1, x2, z1, x1, z2, z1); // OBFHELPER: markBlockRangeForRenderUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light thread-safe.
|
||||
*
|
||||
* @param lightType The type of light to check
|
||||
* @param pos The block position
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLightFor(EnumSkyBlock lightType, BlockPosition pos) {
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((IMixinWorldServer) this.world).updateLightAsync(lightType, pos, chunk);
|
||||
}
|
||||
|
||||
private boolean checkWorldLight(BlockPosition pos) {
|
||||
return this.checkWorldLight(pos, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks world light async.
|
||||
*
|
||||
* @param pos The block position
|
||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||
* @return True if light update was successful, false if not
|
||||
*/
|
||||
private boolean checkWorldLight(BlockPosition pos, List<Chunk> neighbors) {
|
||||
boolean flag = false;
|
||||
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
|
||||
flag |= ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, pos, chunk);
|
||||
}
|
||||
|
||||
flag = flag | ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.BLOCK, pos, chunk);
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of block positions currently queued for lighting updates.
|
||||
*
|
||||
* @param type The light type
|
||||
* @return The list of queued block positions, empty if none
|
||||
*/
|
||||
@Override
|
||||
public CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type) {
|
||||
if (type == EnumSkyBlock.SKY) {
|
||||
return this.queuedSkyLightingUpdates;
|
||||
}
|
||||
return this.queuedBlockLightingUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import io.akarin.api.internal.mixin.IMixinChunk;
|
||||
import net.minecraft.server.ChunkProviderServer;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@Mixin(value = ChunkProviderServer.class, remap = false, priority = 1001)
|
||||
public abstract class MixinChunkProviderServer {
|
||||
@Shadow @Final public WorldServer world;
|
||||
|
||||
@Redirect(method = "unloadChunks", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/Chunk;isUnloading()Z"
|
||||
))
|
||||
public boolean shouldUnload(IMixinChunk chunk) {
|
||||
if (chunk.getPendingLightUpdates().get() > 0 || this.world.getTime() - chunk.getLightUpdateTime() < 20) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IChunkProvider;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.World;
|
||||
|
||||
@Mixin(value = World.class, remap = false, priority = 1002)
|
||||
public abstract class MixinWorld {
|
||||
@Shadow protected IChunkProvider chunkProvider;
|
||||
@Shadow int[] J; // OBFHELPER: lightUpdateBlockList
|
||||
|
||||
@Shadow(aliases = "c") public abstract boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos);
|
||||
@Shadow public abstract MinecraftServer getMinecraftServer();
|
||||
@Shadow public abstract boolean areChunksLoaded(BlockPosition center, int radius, boolean allowEmpty);
|
||||
@Shadow public abstract Chunk getChunkIfLoaded(int x, int z);
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
/*
|
||||
* This file is part of Sponge, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) SpongePowered <https://www.spongepowered.org>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package io.akarin.server.mixin.lighting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.mixin.IMixinChunk;
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.BlockPosition;
|
||||
import net.minecraft.server.Chunk;
|
||||
import net.minecraft.server.EnumDirection;
|
||||
import net.minecraft.server.EnumSkyBlock;
|
||||
import net.minecraft.server.IBlockData;
|
||||
import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.MathHelper;
|
||||
import net.minecraft.server.WorldServer;
|
||||
import net.minecraft.server.BlockPosition.PooledBlockPosition;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false, priority = 1002)
|
||||
public abstract class MixinWorldServer extends MixinWorld implements IMixinWorldServer {
|
||||
|
||||
private static final int NUM_XZ_BITS = 4;
|
||||
private static final int NUM_SHORT_Y_BITS = 8;
|
||||
private static final short XZ_MASK = 0xF;
|
||||
private static final short Y_SHORT_MASK = 0xFF;
|
||||
|
||||
private final static ExecutorService lightExecutorService = getExecutorService();
|
||||
private static ExecutorService getExecutorService() {
|
||||
return AkarinGlobalConfig.asyncLightingWorkStealing ?
|
||||
Executors.newFixedThreadPool(AkarinGlobalConfig.asyncLightingThreads, new ThreadFactoryBuilder().setNameFormat("Akarin Async Light Thread").build())
|
||||
:
|
||||
Executors.newWorkStealingPool(AkarinGlobalConfig.asyncLightingThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos) { // OBFHELPER: checkLightFor
|
||||
return updateLightAsync(lightType, pos, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
// Sponge - This check is not needed as neighbors are checked in updateLightAsync
|
||||
if (false && !this.areChunksLoaded(pos, 17, false)) {
|
||||
return false;
|
||||
} else {
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
int recheckIndex = 0;
|
||||
int blockIndex = 0;
|
||||
int current = this.getLightForAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int rawLight = this.getRawBlockLightAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int x = pos.getX();
|
||||
int y = pos.getY();
|
||||
int z = pos.getZ();
|
||||
|
||||
if (rawLight > current) {
|
||||
this.J[blockIndex++] = 133152; // OBFHELPER: lightUpdateBlockList
|
||||
} else if (rawLight < current) {
|
||||
this.J[blockIndex++] = 133152 | current << 18; // OBFHELPER: lightUpdateBlockList
|
||||
|
||||
while (recheckIndex < blockIndex) {
|
||||
int blockData = this.J[recheckIndex++]; // OBFHELPER: lightUpdateBlockList
|
||||
int i2 = (blockData & 63) - 32 + x;
|
||||
int j2 = (blockData >> 6 & 63) - 32 + y;
|
||||
int k2 = (blockData >> 12 & 63) - 32 + z;
|
||||
int l2 = blockData >> 18 & 15;
|
||||
BlockPosition blockpos = new BlockPosition(i2, j2, k2);
|
||||
int lightLevel = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (lightLevel == l2) {
|
||||
this.setLightForAsync(lightType, blockpos, 0, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (l2 > 0) {
|
||||
int j3 = MathHelper.a(i2 - x); // abs
|
||||
int k3 = MathHelper.a(j2 - y);
|
||||
int l3 = MathHelper.a(k2 - z);
|
||||
|
||||
if (j3 + k3 + l3 < 17) {
|
||||
PooledBlockPosition mutableBlockpos = PooledBlockPosition.aquire();
|
||||
|
||||
for (EnumDirection enumfacing : EnumDirection.values()) {
|
||||
int i4 = i2 + enumfacing.getAdjacentX();
|
||||
int j4 = j2 + enumfacing.getAdjacentX();
|
||||
int k4 = k2 + enumfacing.getAdjacentX();
|
||||
mutableBlockpos.setValues(i4, j4, k4);
|
||||
// Sponge start - get chunk safely
|
||||
final Chunk pooledChunk = this.getLightChunk(mutableBlockpos, currentChunk, neighbors);
|
||||
if (pooledChunk == null) {
|
||||
continue;
|
||||
}
|
||||
int opacity = Math.max(1, pooledChunk.getBlockData(mutableBlockpos).c()); // OBFHELPER: getLightOpacity
|
||||
lightLevel = this.getLightForAsync(lightType, mutableBlockpos, currentChunk, neighbors);
|
||||
// Sponge end
|
||||
|
||||
if (lightLevel == l2 - opacity && blockIndex < this.J.length) { // OBFHELPER: lightUpdateBlockList
|
||||
this.J[blockIndex++] = i4 - x + 32 | j4 - y + 32 << 6 | k4 - z + 32 << 12 | l2 - opacity << 18; // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
}
|
||||
|
||||
mutableBlockpos.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recheckIndex = 0;
|
||||
}
|
||||
|
||||
while (recheckIndex < blockIndex) {
|
||||
int i5 = this.J[recheckIndex++]; // OBFHELPER: lightUpdateBlockList
|
||||
int j5 = (i5 & 63) - 32 + x;
|
||||
int k5 = (i5 >> 6 & 63) - 32 + y;
|
||||
int l5 = (i5 >> 12 & 63) - 32 + z;
|
||||
BlockPosition blockpos1 = new BlockPosition(j5, k5, l5);
|
||||
int i6 = this.getLightForAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
int j6 = this.getRawBlockLightAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 != i6) {
|
||||
this.setLightForAsync(lightType, blockpos1, j6, currentChunk, neighbors); // Sponge - use thread safe method
|
||||
|
||||
if (j6 > i6) {
|
||||
int k6 = Math.abs(j5 - x);
|
||||
int l6 = Math.abs(k5 - y);
|
||||
int i7 = Math.abs(l5 - z);
|
||||
boolean flag = blockIndex < this.J.length - 6; // OBFHELPER: lightUpdateBlockList
|
||||
|
||||
if (k6 + l6 + i7 < 17 && flag) {
|
||||
// Sponge start - use thread safe method getLightForAsync
|
||||
if (this.getLightForAsync(lightType, blockpos1.west(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 - 1 - x + 32 + (k5 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.east(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 + 1 - x + 32 + (k5 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.down(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 - x + 32 + (k5 - 1 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.up(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 - x + 32 + (k5 + 1 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.north(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 - x + 32 + (k5 - y + 32 << 6) + (l5 - 1 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
|
||||
if (this.getLightForAsync(lightType, blockpos1.south(), currentChunk, neighbors) < j6) {
|
||||
this.J[blockIndex++] = j5 - x + 32 + (k5 - y + 32 << 6) + (l5 + 1 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
|
||||
}
|
||||
// Sponge end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sponge start - Asynchronous light updates
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).remove((Short) this.blockPosToShort(pos));
|
||||
spongeChunk.getPendingLightUpdates().decrementAndGet();
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().decrementAndGet();
|
||||
}
|
||||
// Sponge end
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, @Nullable Chunk currentChunk) {
|
||||
if (this.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentChunk == null) {
|
||||
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
|
||||
}
|
||||
|
||||
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
|
||||
if (currentChunk == null || currentChunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final short shortPos = this.blockPosToShort(pos);
|
||||
if (spongeChunk.getQueuedLightingUpdates(lightType).contains(shortPos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Chunk chunk = currentChunk;
|
||||
spongeChunk.getQueuedLightingUpdates(lightType).add(shortPos);
|
||||
spongeChunk.getPendingLightUpdates().incrementAndGet();
|
||||
spongeChunk.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
|
||||
List<Chunk> neighbors = spongeChunk.getNeighbors();
|
||||
// add diagonal chunks
|
||||
Chunk southEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(2);
|
||||
Chunk southWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(3);
|
||||
Chunk northEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(2);
|
||||
Chunk northWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(3);
|
||||
if (southEastChunk != null) {
|
||||
neighbors.add(southEastChunk);
|
||||
}
|
||||
if (southWestChunk != null) {
|
||||
neighbors.add(southWestChunk);
|
||||
}
|
||||
if (northEastChunk != null) {
|
||||
neighbors.add(northEastChunk);
|
||||
}
|
||||
if (northWestChunk != null) {
|
||||
neighbors.add(northWestChunk);
|
||||
}
|
||||
|
||||
for (Chunk neighborChunk : neighbors) {
|
||||
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
|
||||
neighbor.getPendingLightUpdates().incrementAndGet();
|
||||
neighbor.setLightUpdateTime(chunk.getWorld().getTime());
|
||||
}
|
||||
|
||||
if (Akari.isPrimaryThread()) { // Akarin
|
||||
lightExecutorService.execute(() -> {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
});
|
||||
} else {
|
||||
this.checkLightAsync(lightType, pos, chunk, neighbors);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getLightingExecutor() {
|
||||
return lightExecutorService;
|
||||
}
|
||||
|
||||
// Thread safe methods to retrieve a chunk during async light updates
|
||||
// Each method avoids calling getLoadedChunk and instead accesses the passed neighbor chunk list to avoid concurrency issues
|
||||
public Chunk getLightChunk(BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (currentChunk.a(pos.getX() >> 4, pos.getZ() >> 4)) { // OBFHELPER: isAtLocation
|
||||
if (currentChunk.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return currentChunk;
|
||||
}
|
||||
for (Chunk neighbor : neighbors) {
|
||||
if (neighbor.a(pos.getX() >> 4, pos.getZ() >> 4)) { // OBFHELPER: isAtLocation
|
||||
if (neighbor.isUnloading()) {
|
||||
return null;
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getLightForAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.getY() < 0) {
|
||||
pos = new BlockPosition(pos.getX(), 0, pos.getZ());
|
||||
}
|
||||
if (!pos.isValidLocation()) {
|
||||
return lightType.c; // OBFHELPER: defaultLightValue
|
||||
}
|
||||
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // OBFHELPER: defaultLightValue
|
||||
}
|
||||
|
||||
return chunk.getBrightness(lightType, pos);
|
||||
}
|
||||
|
||||
private int getRawBlockLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
final Chunk chunk = getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk == null || chunk.isUnloading()) {
|
||||
return lightType.c; // OBFHELPER: defaultLightValue
|
||||
}
|
||||
if (lightType == EnumSkyBlock.SKY && chunk.c(pos)) { // OBFHELPER: canSeeSky
|
||||
return 15;
|
||||
} else {
|
||||
IBlockData blockData = chunk.getBlockData(pos);
|
||||
int blockLight = blockData.d(); // getLightValue
|
||||
int rawLight = lightType == EnumSkyBlock.SKY ? 0 : blockLight;
|
||||
int opacity = blockData.c(); // OBFHELPER: getLightOpacity
|
||||
|
||||
if (opacity >= 15 && blockLight > 0) {
|
||||
opacity = 1;
|
||||
}
|
||||
|
||||
if (opacity < 1) {
|
||||
opacity = 1;
|
||||
}
|
||||
|
||||
if (opacity >= 15) {
|
||||
return 0;
|
||||
} else if (rawLight >= 14) {
|
||||
return rawLight;
|
||||
} else {
|
||||
for (EnumDirection facing : EnumDirection.values()) {
|
||||
BlockPosition blockpos = pos.shift(facing);
|
||||
int current = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors) - opacity;
|
||||
|
||||
if (current > rawLight) {
|
||||
rawLight = current;
|
||||
}
|
||||
|
||||
if (rawLight >= 14) {
|
||||
return rawLight;
|
||||
}
|
||||
}
|
||||
|
||||
return rawLight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightForAsync(EnumSkyBlock type, BlockPosition pos, int lightValue, Chunk currentChunk, List<Chunk> neighbors) {
|
||||
if (pos.isValidLocation()) {
|
||||
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
|
||||
if (chunk != null && !chunk.isUnloading()) {
|
||||
chunk.a(type, pos, lightValue); // OBFHELPER: setLightFor
|
||||
// this.notifyLightSet(pos); // client side
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private short blockPosToShort(BlockPosition pos) {
|
||||
short serialized = (short) setNibble(0, pos.getX() & XZ_MASK, 0, NUM_XZ_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getY() & Y_SHORT_MASK, 1, NUM_SHORT_Y_BITS);
|
||||
serialized = (short) setNibble(serialized, pos.getZ() & XZ_MASK, 3, NUM_XZ_BITS);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies bits in an integer.
|
||||
*
|
||||
* @param num Integer to modify
|
||||
* @param data Bits of data to add
|
||||
* @param which Index of nibble to start at
|
||||
* @param bitsToReplace The number of bits to replace starting from nibble index
|
||||
* @return The modified integer
|
||||
*/
|
||||
private int setNibble(int num, int data, int which, int bitsToReplace) {
|
||||
return (num & ~(bitsToReplace << (which * 4)) | (data << (which * 4)));
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,7 @@ public abstract class PandaRedstoneWire extends Block {
|
||||
|
||||
while (!turnOff.isEmpty()) {
|
||||
BlockPosition pos = turnOff.remove(0);
|
||||
if (pos == null) continue; // Akarin
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
this.canProvidePower = false;
|
||||
@@ -185,6 +186,7 @@ public abstract class PandaRedstoneWire extends Block {
|
||||
// Now all needed wires are turned off. Time to turn them on again if there is a power source.
|
||||
while (!this.turnOn.isEmpty()) {
|
||||
BlockPosition pos = this.turnOn.remove(0);
|
||||
if (pos == null) continue; // Akarin
|
||||
IBlockData state = worldIn.getType(pos);
|
||||
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
|
||||
this.canProvidePower = false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,623 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue; // Paper
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
// Spigot start
|
||||
import java.util.function.Supplier;
|
||||
import org.spigotmc.SupplierUtils;
|
||||
// Spigot end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Removes unneed synchronization (performance)
|
||||
*/
|
||||
public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
|
||||
|
||||
private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>(); // Paper - Chunk queue improvements
|
||||
private final Object lock = new Object(); // Paper - Chunk queue improvements
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
private final Map<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = Maps.newConcurrentMap(); // Spigot
|
||||
// CraftBukkit
|
||||
// private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(Maps.newConcurrentMap());
|
||||
private final File d;
|
||||
private final DataConverterManager e;
|
||||
// private boolean f;
|
||||
// CraftBukkit
|
||||
private static final double SAVE_QUEUE_TARGET_SIZE = 625; // Spigot
|
||||
|
||||
public ChunkRegionLoader(File file, DataConverterManager dataconvertermanager) {
|
||||
this.d = file;
|
||||
this.e = dataconvertermanager;
|
||||
}
|
||||
|
||||
// Paper start
|
||||
private long queuedSaves = 0;
|
||||
private final java.util.concurrent.atomic.AtomicLong processedSaves = new java.util.concurrent.atomic.AtomicLong(0L);
|
||||
public int getQueueSize() { return queue.size(); }
|
||||
public long getQueuedSaves() { return queuedSaves; }
|
||||
public long getProcessedSaves() { return processedSaves.longValue(); }
|
||||
// Paper end
|
||||
|
||||
// CraftBukkit start - Add async variant, provide compatibility
|
||||
@Nullable
|
||||
public Chunk a(World world, int i, int j) throws IOException {
|
||||
world.timings.syncChunkLoadDataTimer.startTiming(); // Spigot
|
||||
Object[] data = loadChunk(world, i, j);
|
||||
world.timings.syncChunkLoadDataTimer.stopTiming(); // Spigot
|
||||
if (data != null) {
|
||||
Chunk chunk = (Chunk) data[0];
|
||||
NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
|
||||
loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object[] loadChunk(World world, int i, int j) throws IOException {
|
||||
// CraftBukkit end
|
||||
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
|
||||
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.b.get(chunkcoordintpair)); // Spigot
|
||||
|
||||
if (nbttagcompound == null) {
|
||||
// CraftBukkit start
|
||||
nbttagcompound = RegionFileCache.d(this.d, i, j);
|
||||
|
||||
if (nbttagcompound == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, nbttagcompound);
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
return this.a(world, i, j, nbttagcompound);
|
||||
}
|
||||
|
||||
public boolean chunkExists(int i, int j) {
|
||||
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
|
||||
Supplier<NBTTagCompound> nbttagcompound = this.b.get(chunkcoordintpair); // Spigot
|
||||
|
||||
return nbttagcompound != null ? true : RegionFileCache.chunkExists(this.d, i, j);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
|
||||
if (!nbttagcompound.hasKeyOfType("Level", 10)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
|
||||
return null;
|
||||
} else {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
|
||||
|
||||
if (!nbttagcompound1.hasKeyOfType("Sections", 9)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is missing block data, skipping", Integer.valueOf(i), Integer.valueOf(j));
|
||||
return null;
|
||||
} else {
|
||||
Chunk chunk = this.a(world, nbttagcompound1);
|
||||
|
||||
if (!chunk.a(i, j)) {
|
||||
ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
|
||||
nbttagcompound1.setInt("xPos", i);
|
||||
nbttagcompound1.setInt("zPos", j);
|
||||
|
||||
// CraftBukkit start - Have to move tile entities since we don't load them at this stage
|
||||
NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
|
||||
if (tileEntities != null) {
|
||||
for (int te = 0; te < tileEntities.size(); te++) {
|
||||
NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
|
||||
int x = tileEntity.getInt("x") - chunk.locX * 16;
|
||||
int z = tileEntity.getInt("z") - chunk.locZ * 16;
|
||||
tileEntity.setInt("x", i * 16 + x);
|
||||
tileEntity.setInt("z", j * 16 + z);
|
||||
}
|
||||
}
|
||||
// CraftBukkit end
|
||||
chunk = this.a(world, nbttagcompound1);
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
Object[] data = new Object[2];
|
||||
data[0] = chunk;
|
||||
data[1] = nbttagcompound;
|
||||
return data;
|
||||
// CraftBukkit end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void saveChunk(World world, Chunk chunk, boolean unloaded) throws IOException, ExceptionWorldConflict { // Spigot
|
||||
world.checkSession();
|
||||
|
||||
try {
|
||||
NBTTagCompound nbttagcompound = new NBTTagCompound();
|
||||
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
|
||||
|
||||
nbttagcompound.set("Level", nbttagcompound1);
|
||||
nbttagcompound.setInt("DataVersion", 1343);
|
||||
|
||||
// Spigot start
|
||||
final long worldTime = world.getTime();
|
||||
final boolean worldHasSkyLight = world.worldProvider.m();
|
||||
saveEntities(nbttagcompound1, chunk, world);
|
||||
Supplier<NBTTagCompound> completion = new Supplier<NBTTagCompound>() {
|
||||
public NBTTagCompound get() {
|
||||
saveBody(nbttagcompound1, chunk, worldTime, worldHasSkyLight);
|
||||
return nbttagcompound;
|
||||
}
|
||||
};
|
||||
|
||||
this.a(chunk.k(), SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
|
||||
// Spigot end
|
||||
} catch (Exception exception) {
|
||||
ChunkRegionLoader.a.error("Failed to save chunk", exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
|
||||
// CraftBukkit
|
||||
// if (!this.c.contains(chunkcoordintpair))
|
||||
synchronized (lock) { // Paper - Chunk queue improvements
|
||||
this.b.put(chunkcoordintpair, nbttagcompound);
|
||||
}
|
||||
queuedSaves++; // Paper
|
||||
queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
|
||||
|
||||
FileIOThread.a().a(this);
|
||||
}
|
||||
|
||||
public boolean a() {
|
||||
// CraftBukkit start
|
||||
return this.processSaveQueueEntry(false);
|
||||
}
|
||||
|
||||
private /*synchronized*/ boolean processSaveQueueEntry(boolean logCompletion) { // Akarin - remove synchronization
|
||||
// CraftBukkit start
|
||||
// Paper start - Chunk queue improvements
|
||||
QueuedChunk chunk = queue.poll();
|
||||
if (chunk == null) {
|
||||
// Paper - end
|
||||
if (logCompletion) {
|
||||
// CraftBukkit end
|
||||
ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.d.getName());
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
|
||||
processedSaves.incrementAndGet(); // Paper
|
||||
|
||||
boolean flag;
|
||||
|
||||
try {
|
||||
// this.c.add(chunkcoordintpair);
|
||||
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(chunk.compoundSupplier); // Spigot // Paper
|
||||
// CraftBukkit
|
||||
|
||||
if (nbttagcompound != null) {
|
||||
int attempts = 0; Exception laste = null; while (attempts++ < 5) { // Paper
|
||||
try {
|
||||
this.b(chunkcoordintpair, nbttagcompound);
|
||||
laste = null; break; // Paper
|
||||
} catch (Exception exception) {
|
||||
//ChunkRegionLoader.a.error("Failed to save chunk", exception); // Paper
|
||||
laste = exception; // Paper
|
||||
}
|
||||
try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();} } // Paper
|
||||
if (laste != null) { com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste); MinecraftServer.LOGGER.error("Failed to save chunk", laste); } // Paper
|
||||
}
|
||||
synchronized (lock) { if (this.b.get(chunkcoordintpair) == chunk.compoundSupplier) { this.b.remove(chunkcoordintpair); } }// Paper - This will not equal if a newer version is still pending
|
||||
|
||||
flag = true;
|
||||
} finally {
|
||||
//this.b.remove(chunkcoordintpair, value); // CraftBukkit // Spigot // Paper
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
||||
private void b(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException {
|
||||
// CraftBukkit start
|
||||
RegionFileCache.e(this.d, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound);
|
||||
|
||||
/*
|
||||
NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
|
||||
dataoutputstream.close();
|
||||
*/
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
public void b(World world, Chunk chunk) throws IOException {}
|
||||
|
||||
public void b() {}
|
||||
|
||||
public void c() {
|
||||
try {
|
||||
// this.f = true; // CraftBukkit
|
||||
|
||||
while (true) {
|
||||
if (this.processSaveQueueEntry(true)) { // CraftBukkit
|
||||
continue;
|
||||
}
|
||||
break; // CraftBukkit - Fix infinite loop when saving chunks
|
||||
}
|
||||
} finally {
|
||||
// this.f = false; // CraftBukkit
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void a(DataConverterManager dataconvertermanager) {
|
||||
dataconvertermanager.a(DataConverterTypes.CHUNK, new DataInspector() {
|
||||
public NBTTagCompound a(DataConverter dataconverter, NBTTagCompound nbttagcompound, int i) {
|
||||
if (nbttagcompound.hasKeyOfType("Level", 10)) {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
|
||||
NBTTagList nbttaglist;
|
||||
int j;
|
||||
|
||||
if (nbttagcompound1.hasKeyOfType("Entities", 9)) {
|
||||
nbttaglist = nbttagcompound1.getList("Entities", 10);
|
||||
|
||||
for (j = 0; j < nbttaglist.size(); ++j) {
|
||||
nbttaglist.a(j, dataconverter.a(DataConverterTypes.ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
|
||||
}
|
||||
}
|
||||
|
||||
if (nbttagcompound1.hasKeyOfType("TileEntities", 9)) {
|
||||
nbttaglist = nbttagcompound1.getList("TileEntities", 10);
|
||||
|
||||
for (j = 0; j < nbttaglist.size(); ++j) {
|
||||
nbttaglist.a(j, dataconverter.a(DataConverterTypes.BLOCK_ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nbttagcompound;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void saveBody(NBTTagCompound nbttagcompound, Chunk chunk, long worldTime, boolean worldHasSkyLight) { // Spigot
|
||||
nbttagcompound.setInt("xPos", chunk.locX);
|
||||
nbttagcompound.setInt("zPos", chunk.locZ);
|
||||
nbttagcompound.setLong("LastUpdate", worldTime); // Spigot
|
||||
nbttagcompound.setIntArray("HeightMap", chunk.r());
|
||||
nbttagcompound.setBoolean("TerrainPopulated", chunk.isDone());
|
||||
nbttagcompound.setBoolean("LightPopulated", chunk.v());
|
||||
nbttagcompound.setLong("InhabitedTime", chunk.x());
|
||||
ChunkSection[] achunksection = chunk.getSections();
|
||||
NBTTagList nbttaglist = new NBTTagList();
|
||||
boolean flag = worldHasSkyLight; // Spigot
|
||||
ChunkSection[] achunksection1 = achunksection;
|
||||
int i = achunksection.length;
|
||||
|
||||
NBTTagCompound nbttagcompound1;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
ChunkSection chunksection = achunksection1[j];
|
||||
|
||||
if (chunksection != Chunk.a) {
|
||||
nbttagcompound1 = new NBTTagCompound();
|
||||
nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255));
|
||||
byte[] abyte = new byte[4096];
|
||||
NibbleArray nibblearray = new NibbleArray();
|
||||
NibbleArray nibblearray1 = chunksection.getBlocks().exportData(abyte, nibblearray);
|
||||
|
||||
nbttagcompound1.setByteArray("Blocks", abyte);
|
||||
nbttagcompound1.setByteArray("Data", nibblearray.asBytes());
|
||||
if (nibblearray1 != null) {
|
||||
nbttagcompound1.setByteArray("Add", nibblearray1.asBytes());
|
||||
}
|
||||
|
||||
nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().asBytes());
|
||||
if (flag) {
|
||||
nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().asBytes());
|
||||
} else {
|
||||
nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().asBytes().length]);
|
||||
}
|
||||
|
||||
nbttaglist.add(nbttagcompound1);
|
||||
}
|
||||
}
|
||||
|
||||
nbttagcompound.set("Sections", nbttaglist);
|
||||
nbttagcompound.setByteArray("Biomes", chunk.getBiomeIndex());
|
||||
|
||||
// Spigot start - End this method here and split off entity saving to another method
|
||||
}
|
||||
|
||||
private static void saveEntities(NBTTagCompound nbttagcompound, Chunk chunk, World world) {
|
||||
int i;
|
||||
NBTTagCompound nbttagcompound1;
|
||||
// Spigot end
|
||||
|
||||
chunk.g(false);
|
||||
NBTTagList nbttaglist1 = new NBTTagList();
|
||||
|
||||
Iterator iterator;
|
||||
|
||||
List<Entity> toUpdate = new java.util.ArrayList<>(); // Paper
|
||||
for (i = 0; i < chunk.getEntitySlices().length; ++i) {
|
||||
iterator = chunk.getEntitySlices()[i].iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity = (Entity) iterator.next();
|
||||
// Paper start
|
||||
if ((int)Math.floor(entity.locX) >> 4 != chunk.locX || (int)Math.floor(entity.locZ) >> 4 != chunk.locZ) {
|
||||
LogManager.getLogger().warn(entity + " is not in this chunk, skipping save. This a bug fix to a vanilla bug. Do not report this to PaperMC please.");
|
||||
toUpdate.add(entity);
|
||||
continue;
|
||||
}
|
||||
if (entity.dead) {
|
||||
continue;
|
||||
}
|
||||
// Paper end
|
||||
|
||||
nbttagcompound1 = new NBTTagCompound();
|
||||
if (entity.d(nbttagcompound1)) {
|
||||
chunk.g(true);
|
||||
nbttaglist1.add(nbttagcompound1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Paper start - move entities to the correct chunk
|
||||
for (Entity entity : toUpdate) {
|
||||
world.entityJoinedWorld(entity, false);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
nbttagcompound.set("Entities", nbttaglist1);
|
||||
NBTTagList nbttaglist2 = new NBTTagList();
|
||||
|
||||
iterator = chunk.getTileEntities().values().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
TileEntity tileentity = (TileEntity) iterator.next();
|
||||
|
||||
nbttagcompound1 = tileentity.save(new NBTTagCompound());
|
||||
nbttaglist2.add(nbttagcompound1);
|
||||
}
|
||||
|
||||
nbttagcompound.set("TileEntities", nbttaglist2);
|
||||
List list = world.a(chunk, false);
|
||||
|
||||
if (list != null) {
|
||||
long k = world.getTime();
|
||||
NBTTagList nbttaglist3 = new NBTTagList();
|
||||
Iterator iterator1 = list.iterator();
|
||||
|
||||
while (iterator1.hasNext()) {
|
||||
NextTickListEntry nextticklistentry = (NextTickListEntry) iterator1.next();
|
||||
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
|
||||
MinecraftKey minecraftkey = (MinecraftKey) Block.REGISTRY.b(nextticklistentry.a());
|
||||
|
||||
nbttagcompound2.setString("i", minecraftkey == null ? "" : minecraftkey.toString());
|
||||
nbttagcompound2.setInt("x", nextticklistentry.a.getX());
|
||||
nbttagcompound2.setInt("y", nextticklistentry.a.getY());
|
||||
nbttagcompound2.setInt("z", nextticklistentry.a.getZ());
|
||||
nbttagcompound2.setInt("t", (int) (nextticklistentry.b - k));
|
||||
nbttagcompound2.setInt("p", nextticklistentry.c);
|
||||
nbttaglist3.add(nbttagcompound2);
|
||||
}
|
||||
|
||||
nbttagcompound.set("TileTicks", nbttaglist3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Chunk a(World world, NBTTagCompound nbttagcompound) {
|
||||
int i = nbttagcompound.getInt("xPos");
|
||||
int j = nbttagcompound.getInt("zPos");
|
||||
Chunk chunk = new Chunk(world, i, j);
|
||||
|
||||
chunk.a(nbttagcompound.getIntArray("HeightMap"));
|
||||
chunk.d(nbttagcompound.getBoolean("TerrainPopulated"));
|
||||
chunk.e(nbttagcompound.getBoolean("LightPopulated"));
|
||||
chunk.c(nbttagcompound.getLong("InhabitedTime"));
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Sections", 10);
|
||||
boolean flag = true;
|
||||
ChunkSection[] achunksection = new ChunkSection[16];
|
||||
boolean flag1 = world.worldProvider.m();
|
||||
|
||||
for (int k = 0; k < nbttaglist.size(); ++k) {
|
||||
NBTTagCompound nbttagcompound1 = nbttaglist.get(k);
|
||||
byte b0 = nbttagcompound1.getByte("Y");
|
||||
ChunkSection chunksection = new ChunkSection(b0 << 4, flag1, world.chunkPacketBlockController.getPredefinedBlockData(chunk, b0)); // Paper - Anti-Xray - Add predefined block data
|
||||
byte[] abyte = nbttagcompound1.getByteArray("Blocks");
|
||||
NibbleArray nibblearray = new NibbleArray(nbttagcompound1.getByteArray("Data"));
|
||||
NibbleArray nibblearray1 = nbttagcompound1.hasKeyOfType("Add", 7) ? new NibbleArray(nbttagcompound1.getByteArray("Add")) : null;
|
||||
|
||||
chunksection.getBlocks().a(abyte, nibblearray, nibblearray1);
|
||||
chunksection.a(new NibbleArray(nbttagcompound1.getByteArray("BlockLight")));
|
||||
if (flag1) {
|
||||
chunksection.b(new NibbleArray(nbttagcompound1.getByteArray("SkyLight")));
|
||||
}
|
||||
|
||||
chunksection.recalcBlockCounts();
|
||||
achunksection[b0] = chunksection;
|
||||
}
|
||||
|
||||
chunk.a(achunksection);
|
||||
if (nbttagcompound.hasKeyOfType("Biomes", 7)) {
|
||||
chunk.a(nbttagcompound.getByteArray("Biomes"));
|
||||
}
|
||||
|
||||
// CraftBukkit start - End this method here and split off entity loading to another method
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
|
||||
// CraftBukkit end
|
||||
world.timings.syncChunkLoadNBTTimer.startTiming(); // Spigot
|
||||
NBTTagList nbttaglist1 = nbttagcompound.getList("Entities", 10);
|
||||
|
||||
for (int l = 0; l < nbttaglist1.size(); ++l) {
|
||||
NBTTagCompound nbttagcompound2 = nbttaglist1.get(l);
|
||||
|
||||
a(nbttagcompound2, world, chunk);
|
||||
chunk.g(true);
|
||||
}
|
||||
NBTTagList nbttaglist2 = nbttagcompound.getList("TileEntities", 10);
|
||||
|
||||
for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
|
||||
NBTTagCompound nbttagcompound3 = nbttaglist2.get(i1);
|
||||
TileEntity tileentity = TileEntity.create(world, nbttagcompound3);
|
||||
|
||||
if (tileentity != null) {
|
||||
chunk.a(tileentity);
|
||||
}
|
||||
}
|
||||
|
||||
if (nbttagcompound.hasKeyOfType("TileTicks", 9)) {
|
||||
NBTTagList nbttaglist3 = nbttagcompound.getList("TileTicks", 10);
|
||||
|
||||
for (int j1 = 0; j1 < nbttaglist3.size(); ++j1) {
|
||||
NBTTagCompound nbttagcompound4 = nbttaglist3.get(j1);
|
||||
Block block;
|
||||
|
||||
if (nbttagcompound4.hasKeyOfType("i", 8)) {
|
||||
block = Block.getByName(nbttagcompound4.getString("i"));
|
||||
} else {
|
||||
block = Block.getById(nbttagcompound4.getInt("i"));
|
||||
}
|
||||
|
||||
world.b(new BlockPosition(nbttagcompound4.getInt("x"), nbttagcompound4.getInt("y"), nbttagcompound4.getInt("z")), block, nbttagcompound4.getInt("t"), nbttagcompound4.getInt("p"));
|
||||
}
|
||||
}
|
||||
world.timings.syncChunkLoadNBTTimer.stopTiming(); // Spigot
|
||||
|
||||
// return chunk; // CraftBukkit
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, Chunk chunk) {
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
chunk.a(entity);
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, chunk);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
// CraftBukkit start
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag) {
|
||||
return spawnEntity(nbttagcompound, world, d0, d1, d2, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
|
||||
}
|
||||
|
||||
public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
|
||||
// CraftBukkit end
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
entity.setPositionRotation(d0, d1, d2, entity.yaw, entity.pitch);
|
||||
if (flag && !world.addEntity(entity, spawnReason)) { // CraftBukkit
|
||||
return null;
|
||||
} else {
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, d0, d1, d2, flag);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static Entity a(NBTTagCompound nbttagcompound, World world) {
|
||||
try {
|
||||
return EntityTypes.a(nbttagcompound, world);
|
||||
} catch (RuntimeException runtimeexception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// CraftBukkit start
|
||||
public static void a(Entity entity, World world) {
|
||||
a(entity, world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
|
||||
}
|
||||
|
||||
public static void a(Entity entity, World world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
|
||||
if (!entity.valid && world.addEntity(entity, reason) && entity.isVehicle()) { // Paper
|
||||
// CraftBukkit end
|
||||
Iterator iterator = entity.bF().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entity entity1 = (Entity) iterator.next();
|
||||
|
||||
a(entity1, world);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity a(NBTTagCompound nbttagcompound, World world, boolean flag) {
|
||||
Entity entity = a(nbttagcompound, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else if (flag && !world.addEntity(entity)) {
|
||||
return null;
|
||||
} else {
|
||||
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
|
||||
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
|
||||
|
||||
for (int i = 0; i < nbttaglist.size(); ++i) {
|
||||
Entity entity1 = a(nbttaglist.get(i), world, flag);
|
||||
|
||||
if (entity1 != null) {
|
||||
entity1.a(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
// Paper start - Chunk queue improvements
|
||||
private static class QueuedChunk {
|
||||
public ChunkCoordIntPair coords;
|
||||
public Supplier<NBTTagCompound> compoundSupplier;
|
||||
|
||||
public QueuedChunk(ChunkCoordIntPair coords, Supplier<NBTTagCompound> compoundSupplier) {
|
||||
this.coords = coords;
|
||||
this.compoundSupplier = compoundSupplier;
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
@@ -27,6 +27,8 @@ import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
import co.aikar.timings.MinecraftTimings; // Paper
|
||||
import co.aikar.timings.Timing; // Paper
|
||||
import io.akarin.api.internal.mixin.IMixinWorldServer;
|
||||
|
||||
import org.bukkit.event.entity.EntityCombustByEntityEvent;
|
||||
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
|
||||
import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
|
||||
@@ -51,7 +53,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
// CraftBukkit start
|
||||
private static final int CURRENT_LEVEL = 2;
|
||||
// Paper start
|
||||
public static Random SHARED_RANDOM = new io.akarin.api.internal.utils.random.LightRandom() { // Akarin - LightRNG
|
||||
public static Random SHARED_RANDOM = new java.util.Random() {
|
||||
private boolean locked = false;
|
||||
@Override
|
||||
public synchronized void setSeed(long seed) {
|
||||
@@ -85,7 +87,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
private static final List<ItemStack> b = Collections.emptyList();
|
||||
private static final AxisAlignedBB c = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
|
||||
private static double f = 1.0D;
|
||||
private static int entityCount;
|
||||
private static int entityCount = 1; // Paper - MC-111480 - ID 0 is treated as special for DataWatchers, start 1
|
||||
private int id;
|
||||
public boolean i; public boolean blocksEntitySpawning() { return i; } // Paper - OBFHELPER
|
||||
public final List<Entity> passengers;
|
||||
@@ -128,6 +130,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
protected boolean E;
|
||||
private boolean aw;
|
||||
public boolean dead;
|
||||
public boolean shouldBeRemoved; // Paper
|
||||
public float width;
|
||||
public float length;
|
||||
public float I;
|
||||
@@ -207,7 +210,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
this.length = 1.8F;
|
||||
this.ax = 1;
|
||||
this.ay = 1.0F;
|
||||
this.random = SHARED_RANDOM; // Paper
|
||||
this.random = ((IMixinWorldServer) world).rand(); // Paper // Akarin
|
||||
this.fireTicks = -this.getMaxFireTicks();
|
||||
this.justCreated = true;
|
||||
this.uniqueID = MathHelper.a(this.random);
|
||||
@@ -344,6 +347,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
this.locX = d0;
|
||||
this.locY = d1;
|
||||
this.locZ = d2;
|
||||
if (valid) world.entityJoinedWorld(this, false); // Paper - ensure Entity is moved to its proper chunk
|
||||
float f = this.width / 2.0F;
|
||||
float f1 = this.length;
|
||||
|
||||
@@ -989,6 +993,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
this.locX = (axisalignedbb.a + axisalignedbb.d) / 2.0D;
|
||||
this.locY = axisalignedbb.b;
|
||||
this.locZ = (axisalignedbb.c + axisalignedbb.f) / 2.0D;
|
||||
if (valid) world.entityJoinedWorld(this, false); // Paper - ensure Entity is moved to its proper chunk
|
||||
}
|
||||
|
||||
protected SoundEffect ae() {
|
||||
@@ -1310,6 +1315,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
this.lastYaw -= 360.0F;
|
||||
}
|
||||
|
||||
world.getChunkAt((int) Math.floor(this.locX) >> 4, (int) Math.floor(this.locZ) >> 4); // Paper - ensure chunk is always loaded
|
||||
this.setPosition(this.locX, this.locY, this.locZ);
|
||||
this.setYawPitch(f, f1);
|
||||
}
|
||||
@@ -1791,7 +1797,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
*/
|
||||
public Chunk getCurrentChunk() {
|
||||
final Chunk chunk = currentChunk != null ? currentChunk.get() : null;
|
||||
return chunk != null && chunk.isLoaded() ? chunk : null;
|
||||
return chunk != null && chunk.isLoaded() ? chunk : (isAddedToChunk() ? world.getChunkIfLoaded(getChunkX(), getChunkZ()) : null);
|
||||
}
|
||||
/**
|
||||
* Returns the chunk at the location, using the entities local cache if avail
|
||||
@@ -1813,21 +1819,26 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
|
||||
public Chunk getChunkAtLocation() {
|
||||
return getCurrentChunkAt((int)Math.floor(locX) >> 4, (int)Math.floor(locZ) >> 4);
|
||||
}
|
||||
public final MinecraftKey entityKey = EntityTypes.getKey(this);
|
||||
public final String entityKeyString = entityKey != null ? entityKey.toString() : null;
|
||||
private String entityKeyString = null;
|
||||
private MinecraftKey entityKey = getMinecraftKey();
|
||||
|
||||
@Override
|
||||
public MinecraftKey getMinecraftKey() {
|
||||
if (entityKey == null) {
|
||||
entityKey = EntityTypes.getKey(this);
|
||||
entityKeyString = entityKey != null ? entityKey.toString() : null;
|
||||
}
|
||||
return entityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinecraftKeyString() {
|
||||
getMinecraftKey(); // Try to load if it doesn't exists. see: https://github.com/PaperMC/Paper/issues/1280
|
||||
return entityKeyString;
|
||||
}
|
||||
@Nullable
|
||||
public final String getSaveID() {
|
||||
return entityKeyString;
|
||||
return getMinecraftKeyString();
|
||||
// Paper end
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.bukkit.inventory.MainHand;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add volatile to fields (time update)
|
||||
* 2) Add lock to player track (safety issue)
|
||||
*/
|
||||
public class EntityPlayer extends EntityHuman implements ICrafting {
|
||||
@@ -745,9 +744,9 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
||||
if (entity instanceof EntityPlayer) {
|
||||
WorldServer worldServer = (WorldServer) entity.getWorld();
|
||||
worldServer.tracker.untrackEntity(this);
|
||||
worldServer.tracker.entriesLock.lock(); // Akarin
|
||||
worldServer.tracker.entriesLock.writeLock().lock(); // Akarin - ProtocolSupport will overwrite track method
|
||||
worldServer.tracker.track(this);
|
||||
worldServer.tracker.entriesLock.unlock(); // Akarin
|
||||
worldServer.tracker.entriesLock.writeLock().unlock(); // Akarin - ProtocolSupport will overwrite track method
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@@ -1422,8 +1421,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
||||
}
|
||||
|
||||
// CraftBukkit start - Add per-player time and weather.
|
||||
public volatile long timeOffset = 0; // Akarin - volatile
|
||||
public volatile boolean relativeTime = true; // Akarin - volatile
|
||||
public long timeOffset = 0;
|
||||
public boolean relativeTime = true;
|
||||
|
||||
public long getPlayerTime() {
|
||||
if (this.relativeTime) {
|
||||
|
||||
@@ -2,25 +2,28 @@ package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add lock for entries set operations (safety issue)
|
||||
* 1) Made collections and entry access thread-safe (safety issue)
|
||||
*/
|
||||
@ThreadSafe // Akarin
|
||||
public class EntityTracker {
|
||||
|
||||
private static final Logger a = LogManager.getLogger();
|
||||
private final WorldServer world;
|
||||
private final Set<EntityTrackerEntry> c = Sets.newHashSet();
|
||||
public final ReentrantLock entriesLock = new ReentrantLock(); // Akarin - add lock
|
||||
public final ReentrantReadWriteUpdateLock entriesLock = new ReentrantReadWriteUpdateLock(); // Akarin - add lock
|
||||
public final IntHashMap<EntityTrackerEntry> trackedEntities = new IntHashMap();
|
||||
private int e;
|
||||
|
||||
@@ -38,7 +41,7 @@ public class EntityTracker {
|
||||
this.addEntity(entity, 512, 2);
|
||||
EntityPlayer entityplayer = (EntityPlayer) entity;
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
// entriesLock.writeLock().lock(); // Akarin - locked in EntityPlayer
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
@@ -47,7 +50,7 @@ public class EntityTracker {
|
||||
entitytrackerentry.updatePlayer(entityplayer);
|
||||
}
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
// entriesLock.writeLock().unlock(); // Akarin - locked in EntityPlayer
|
||||
} else if (entity instanceof EntityFishingHook) {
|
||||
this.addEntity(entity, 64, 5, true);
|
||||
} else if (entity instanceof EntityArrow) {
|
||||
@@ -118,18 +121,18 @@ public class EntityTracker {
|
||||
org.spigotmc.AsyncCatcher.catchOp( "entity track"); // Spigot
|
||||
i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // 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);
|
||||
|
||||
entriesLock.lock(); // Akarin
|
||||
this.c.add(entitytrackerentry);
|
||||
|
||||
this.trackedEntities.a(entity.getId(), entitytrackerentry);
|
||||
entitytrackerentry.scanPlayers(this.world.players);
|
||||
entriesLock.unlock(); // Akarin
|
||||
// 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");
|
||||
@@ -166,34 +169,32 @@ public class EntityTracker {
|
||||
|
||||
public void untrackEntity(Entity entity) {
|
||||
org.spigotmc.AsyncCatcher.catchOp( "entity untrack"); // Spigot
|
||||
entriesLock.writeLock().lock(); // Akarin
|
||||
if (entity instanceof EntityPlayer) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) entity;
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.a(entityplayer);
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
}
|
||||
|
||||
EntityTrackerEntry entitytrackerentry1 = this.trackedEntities.d(entity.getId());
|
||||
|
||||
if (entitytrackerentry1 != null) {
|
||||
entriesLock.lock(); // Akarin
|
||||
this.c.remove(entitytrackerentry1);
|
||||
entitytrackerentry1.a();
|
||||
entriesLock.unlock(); // Akarin
|
||||
}
|
||||
entriesLock.writeLock().unlock(); // Akarin
|
||||
}
|
||||
|
||||
public void updatePlayers() {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
Iterator iterator = this.c.iterator();
|
||||
world.timings.tracker1.startTiming(); // Spigot
|
||||
entriesLock.lock(); // Akarin
|
||||
entriesLock.writeLock().lock(); // Akarin
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
@@ -221,14 +222,14 @@ public class EntityTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
entriesLock.writeLock().unlock(); // Akarin
|
||||
world.timings.tracker2.stopTiming(); // Spigot
|
||||
|
||||
}
|
||||
|
||||
public void a(EntityPlayer entityplayer) {
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
entriesLock.writeLock().lock(); // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
@@ -239,11 +240,13 @@ public class EntityTracker {
|
||||
entitytrackerentry.updatePlayer(entityplayer);
|
||||
}
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
entriesLock.writeLock().unlock(); // Akarin
|
||||
}
|
||||
|
||||
public void a(Entity entity, Packet<?> packet) {
|
||||
entriesLock.readLock().lock(); // Akarin
|
||||
EntityTrackerEntry entitytrackerentry = this.trackedEntities.get(entity.getId());
|
||||
entriesLock.readLock().unlock(); // Akarin
|
||||
|
||||
if (entitytrackerentry != null) {
|
||||
entitytrackerentry.broadcast(packet);
|
||||
@@ -252,7 +255,9 @@ public class EntityTracker {
|
||||
}
|
||||
|
||||
public void sendPacketToEntity(Entity entity, Packet<?> packet) {
|
||||
entriesLock.readLock().lock(); // Akarin
|
||||
EntityTrackerEntry entitytrackerentry = this.trackedEntities.get(entity.getId());
|
||||
entriesLock.readLock().unlock(); // Akarin
|
||||
|
||||
if (entitytrackerentry != null) {
|
||||
entitytrackerentry.broadcastIncludingSelf(packet);
|
||||
@@ -262,21 +267,21 @@ public class EntityTracker {
|
||||
|
||||
public void untrackPlayer(EntityPlayer entityplayer) {
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
entriesLock.writeLock().lock();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.clear(entityplayer);
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
entriesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
public void a(EntityPlayer entityplayer, Chunk chunk) {
|
||||
ArrayList arraylist = Lists.newArrayList();
|
||||
ArrayList arraylist1 = Lists.newArrayList();
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
entriesLock.writeLock().lock(); // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
@@ -293,7 +298,7 @@ public class EntityTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
entriesLock.writeLock().unlock(); // Akarin
|
||||
|
||||
Entity entity1;
|
||||
|
||||
@@ -320,13 +325,13 @@ public class EntityTracker {
|
||||
public void a(int i) {
|
||||
this.e = (i - 1) * 16;
|
||||
Iterator iterator = this.c.iterator();
|
||||
entriesLock.lock(); // Akarin
|
||||
entriesLock.readLock().lock(); // Akarin
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
|
||||
|
||||
entitytrackerentry.a(this.e);
|
||||
}
|
||||
entriesLock.unlock(); // Akarin
|
||||
entriesLock.readLock().unlock(); // Akarin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,658 +0,0 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
// CraftBukkit start
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerVelocityEvent;
|
||||
// CraftBukkit end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Make trackedPlayerMap thread-safe (safety issue)
|
||||
*/
|
||||
public class EntityTrackerEntry {
|
||||
|
||||
private static final Logger c = LogManager.getLogger();
|
||||
private final Entity tracker;
|
||||
private final int e;
|
||||
private int f;
|
||||
private final int g;
|
||||
private long xLoc;
|
||||
private long yLoc;
|
||||
private long zLoc;
|
||||
private int yRot;
|
||||
private int xRot;
|
||||
private int headYaw;
|
||||
private double n;
|
||||
private double o;
|
||||
private double p;
|
||||
public int a;
|
||||
private double q;
|
||||
private double r;
|
||||
private double s;
|
||||
private boolean isMoving;
|
||||
private final boolean u;
|
||||
private int v;
|
||||
private List<Entity> w = Collections.emptyList();
|
||||
private boolean x;
|
||||
private boolean y;
|
||||
public boolean b;
|
||||
// Paper start
|
||||
// Replace trackedPlayers Set with a Map. The value is true until the player receives
|
||||
// their first update (which is forced to have absolute coordinates), false afterward.
|
||||
public java.util.Map<EntityPlayer, Boolean> trackedPlayerMap = new java.util.concurrent.ConcurrentHashMap<EntityPlayer, Boolean>(); // Akarin - make concurrent
|
||||
public Set<EntityPlayer> trackedPlayers = trackedPlayerMap.keySet();
|
||||
// Paper end
|
||||
|
||||
public EntityTrackerEntry(Entity entity, int i, int j, int k, boolean flag) {
|
||||
entity.tracker = this; // Paper
|
||||
this.tracker = entity;
|
||||
this.e = i;
|
||||
this.f = j;
|
||||
this.g = k;
|
||||
this.u = flag;
|
||||
this.xLoc = EntityTracker.a(entity.locX);
|
||||
this.yLoc = EntityTracker.a(entity.locY);
|
||||
this.zLoc = EntityTracker.a(entity.locZ);
|
||||
this.yRot = MathHelper.d(entity.yaw * 256.0F / 360.0F);
|
||||
this.xRot = MathHelper.d(entity.pitch * 256.0F / 360.0F);
|
||||
this.headYaw = MathHelper.d(entity.getHeadRotation() * 256.0F / 360.0F);
|
||||
this.y = entity.onGround;
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
return object instanceof EntityTrackerEntry ? ((EntityTrackerEntry) object).tracker.getId() == this.tracker.getId() : false;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.tracker.getId();
|
||||
}
|
||||
|
||||
public void track(List<EntityHuman> list) {
|
||||
this.b = false;
|
||||
if (!this.isMoving || this.tracker.d(this.q, this.r, this.s) > 16.0D) {
|
||||
this.q = this.tracker.locX;
|
||||
this.r = this.tracker.locY;
|
||||
this.s = this.tracker.locZ;
|
||||
this.isMoving = true;
|
||||
this.b = true;
|
||||
this.scanPlayers(list);
|
||||
}
|
||||
|
||||
List list1 = this.tracker.bF();
|
||||
|
||||
if (!list1.equals(this.w)) {
|
||||
this.w = list1;
|
||||
this.broadcastIncludingSelf(new PacketPlayOutMount(this.tracker)); // CraftBukkit
|
||||
}
|
||||
|
||||
// PAIL : rename
|
||||
if (this.tracker instanceof EntityItemFrame && this.a % 20 == 0) { // Paper
|
||||
EntityItemFrame entityitemframe = (EntityItemFrame) this.tracker;
|
||||
ItemStack itemstack = entityitemframe.getItem();
|
||||
|
||||
if (itemstack != null && itemstack.getItem() instanceof ItemWorldMap) { // Paper - moved back up
|
||||
WorldMap worldmap = Items.FILLED_MAP.getSavedMap(itemstack, this.tracker.world);
|
||||
Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityHuman entityhuman = (EntityHuman) iterator.next();
|
||||
EntityPlayer entityplayer = (EntityPlayer) entityhuman;
|
||||
|
||||
worldmap.a(entityplayer, itemstack);
|
||||
Packet packet = Items.FILLED_MAP.a(itemstack, this.tracker.world, (EntityHuman) entityplayer);
|
||||
|
||||
if (packet != null) {
|
||||
entityplayer.playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.d();
|
||||
}
|
||||
|
||||
if (this.a % this.g == 0 || this.tracker.impulse || this.tracker.getDataWatcher().a()) {
|
||||
int i;
|
||||
|
||||
if (this.tracker.isPassenger()) {
|
||||
i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F);
|
||||
int j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F);
|
||||
boolean flag = Math.abs(i - this.yRot) >= 1 || Math.abs(j - this.xRot) >= 1;
|
||||
|
||||
if (flag) {
|
||||
this.broadcast(new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) i, (byte) j, this.tracker.onGround));
|
||||
this.yRot = i;
|
||||
this.xRot = j;
|
||||
}
|
||||
|
||||
this.xLoc = EntityTracker.a(this.tracker.locX);
|
||||
this.yLoc = EntityTracker.a(this.tracker.locY);
|
||||
this.zLoc = EntityTracker.a(this.tracker.locZ);
|
||||
this.d();
|
||||
this.x = true;
|
||||
} else {
|
||||
++this.v;
|
||||
long k = EntityTracker.a(this.tracker.locX);
|
||||
long l = EntityTracker.a(this.tracker.locY);
|
||||
long i1 = EntityTracker.a(this.tracker.locZ);
|
||||
int j1 = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F);
|
||||
int k1 = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F);
|
||||
long l1 = k - this.xLoc;
|
||||
long i2 = l - this.yLoc;
|
||||
long j2 = i1 - this.zLoc;
|
||||
Object object = null;
|
||||
boolean flag1 = l1 * l1 + i2 * i2 + j2 * j2 >= 128L || this.a % 60 == 0;
|
||||
boolean flag2 = Math.abs(j1 - this.yRot) >= 1 || Math.abs(k1 - this.xRot) >= 1;
|
||||
|
||||
if (this.a > 0 || this.tracker instanceof EntityArrow) { // Paper - Moved up
|
||||
// CraftBukkit start - Code moved from below
|
||||
if (flag1) {
|
||||
this.xLoc = k;
|
||||
this.yLoc = l;
|
||||
this.zLoc = i1;
|
||||
}
|
||||
|
||||
if (flag2) {
|
||||
this.yRot = j1;
|
||||
this.xRot = k1;
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
if (l1 >= -32768L && l1 < 32768L && i2 >= -32768L && i2 < 32768L && j2 >= -32768L && j2 < 32768L && this.v <= 400 && !this.x && this.y == this.tracker.onGround) {
|
||||
if ((!flag1 || !flag2) && !(this.tracker instanceof EntityArrow)) {
|
||||
if (flag1) {
|
||||
object = new PacketPlayOutEntity.PacketPlayOutRelEntityMove(this.tracker.getId(), l1, i2, j2, this.tracker.onGround);
|
||||
} else if (flag2) {
|
||||
object = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) j1, (byte) k1, this.tracker.onGround);
|
||||
}
|
||||
} else {
|
||||
object = new PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook(this.tracker.getId(), l1, i2, j2, (byte) j1, (byte) k1, this.tracker.onGround);
|
||||
}
|
||||
} else {
|
||||
this.y = this.tracker.onGround;
|
||||
this.v = 0;
|
||||
// CraftBukkit start - Refresh list of who can see a player before sending teleport packet
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
this.scanPlayers(new java.util.ArrayList(this.trackedPlayers));
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.c();
|
||||
object = new PacketPlayOutEntityTeleport(this.tracker);
|
||||
}
|
||||
}
|
||||
|
||||
boolean flag3 = this.u;
|
||||
|
||||
if (this.tracker instanceof EntityLiving && ((EntityLiving) this.tracker).cP()) {
|
||||
flag3 = true;
|
||||
}
|
||||
|
||||
if (flag3 && this.a > 0) {
|
||||
double d0 = this.tracker.motX - this.n;
|
||||
double d1 = this.tracker.motY - this.o;
|
||||
double d2 = this.tracker.motZ - this.p;
|
||||
double d3 = 0.02D;
|
||||
double d4 = d0 * d0 + d1 * d1 + d2 * d2;
|
||||
|
||||
if (d4 > 4.0E-4D || d4 > 0.0D && this.tracker.motX == 0.0D && this.tracker.motY == 0.0D && this.tracker.motZ == 0.0D) {
|
||||
this.n = this.tracker.motX;
|
||||
this.o = this.tracker.motY;
|
||||
this.p = this.tracker.motZ;
|
||||
this.broadcast(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.n, this.o, this.p));
|
||||
}
|
||||
}
|
||||
|
||||
if (object != null) {
|
||||
// Paper start - ensure fresh viewers get an absolute position on their first update,
|
||||
// since we can't be certain what position they received in the spawn packet.
|
||||
if (object instanceof PacketPlayOutEntityTeleport) {
|
||||
this.broadcast((Packet) object);
|
||||
} else {
|
||||
PacketPlayOutEntityTeleport teleportPacket = null;
|
||||
|
||||
for (java.util.Map.Entry<EntityPlayer, Boolean> viewer : trackedPlayerMap.entrySet()) {
|
||||
if (viewer.getValue()) {
|
||||
viewer.setValue(false);
|
||||
if (teleportPacket == null) {
|
||||
teleportPacket = new PacketPlayOutEntityTeleport(this.tracker);
|
||||
}
|
||||
viewer.getKey().playerConnection.sendPacket(teleportPacket);
|
||||
} else {
|
||||
viewer.getKey().playerConnection.sendPacket((Packet) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
|
||||
this.d();
|
||||
/* CraftBukkit start - Code moved up
|
||||
if (flag1) {
|
||||
this.xLoc = k;
|
||||
this.yLoc = l;
|
||||
this.zLoc = i1;
|
||||
}
|
||||
|
||||
if (flag2) {
|
||||
this.yRot = j1;
|
||||
this.xRot = k1;
|
||||
}
|
||||
// CraftBukkit end */
|
||||
|
||||
this.x = false;
|
||||
}
|
||||
|
||||
i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
|
||||
if (Math.abs(i - this.headYaw) >= 1) {
|
||||
this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i));
|
||||
this.headYaw = i;
|
||||
}
|
||||
|
||||
this.tracker.impulse = false;
|
||||
}
|
||||
|
||||
++this.a;
|
||||
if (this.tracker.velocityChanged) {
|
||||
// CraftBukkit start - Create PlayerVelocity event
|
||||
boolean cancelled = false;
|
||||
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
Player player = (Player) this.tracker.getBukkitEntity();
|
||||
org.bukkit.util.Vector velocity = player.getVelocity();
|
||||
|
||||
PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
|
||||
this.tracker.world.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
cancelled = true;
|
||||
} else if (!velocity.equals(event.getVelocity())) {
|
||||
player.setVelocity(event.getVelocity());
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker));
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.tracker.velocityChanged = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void d() {
|
||||
DataWatcher datawatcher = this.tracker.getDataWatcher();
|
||||
|
||||
if (datawatcher.a()) {
|
||||
this.broadcastIncludingSelf(new PacketPlayOutEntityMetadata(this.tracker.getId(), datawatcher, false));
|
||||
}
|
||||
|
||||
if (this.tracker instanceof EntityLiving) {
|
||||
AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).getAttributeMap();
|
||||
Set set = attributemapserver.getAttributes();
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
// CraftBukkit start - Send scaled max health
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(set, false);
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.broadcastIncludingSelf(new PacketPlayOutUpdateAttributes(this.tracker.getId(), set));
|
||||
}
|
||||
|
||||
set.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void broadcast(Packet<?> packet) {
|
||||
Iterator iterator = this.trackedPlayers.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
|
||||
|
||||
entityplayer.playerConnection.sendPacket(packet);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void broadcastIncludingSelf(Packet<?> packet) {
|
||||
this.broadcast(packet);
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
((EntityPlayer) this.tracker).playerConnection.sendPacket(packet);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a() {
|
||||
Iterator iterator = this.trackedPlayers.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
|
||||
|
||||
this.tracker.c(entityplayer);
|
||||
entityplayer.c(this.tracker);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(EntityPlayer entityplayer) {
|
||||
if (this.trackedPlayers.contains(entityplayer)) {
|
||||
this.tracker.c(entityplayer);
|
||||
entityplayer.c(this.tracker);
|
||||
this.trackedPlayers.remove(entityplayer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updatePlayer(EntityPlayer entityplayer) {
|
||||
org.spigotmc.AsyncCatcher.catchOp( "player tracker update"); // Spigot
|
||||
if (entityplayer != this.tracker) {
|
||||
if (this.c(entityplayer)) {
|
||||
if (!this.trackedPlayers.contains(entityplayer) && (this.e(entityplayer) || this.tracker.attachedToPlayer)) {
|
||||
// CraftBukkit start - respect vanish API
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
Player player = ((EntityPlayer) this.tracker).getBukkitEntity();
|
||||
if (!entityplayer.getBukkitEntity().canSee(player)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId()));
|
||||
// CraftBukkit end
|
||||
this.trackedPlayerMap.put(entityplayer, true); // Paper
|
||||
Packet packet = this.e();
|
||||
|
||||
entityplayer.playerConnection.sendPacket(packet);
|
||||
if (!this.tracker.getDataWatcher().d()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityMetadata(this.tracker.getId(), this.tracker.getDataWatcher(), true));
|
||||
}
|
||||
|
||||
boolean flag = this.u;
|
||||
|
||||
if (this.tracker instanceof EntityLiving) {
|
||||
AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).getAttributeMap();
|
||||
Collection collection = attributemapserver.c();
|
||||
|
||||
// CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
|
||||
if (this.tracker.getId() == entityplayer.getId()) {
|
||||
((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(collection, false);
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
if (!collection.isEmpty()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutUpdateAttributes(this.tracker.getId(), collection));
|
||||
}
|
||||
|
||||
if (((EntityLiving) this.tracker).cP()) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.n = this.tracker.motX;
|
||||
this.o = this.tracker.motY;
|
||||
this.p = this.tracker.motZ;
|
||||
if (flag && !(packet instanceof PacketPlayOutSpawnEntityLiving)) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.tracker.motX, this.tracker.motY, this.tracker.motZ));
|
||||
}
|
||||
|
||||
if (this.tracker instanceof EntityLiving) {
|
||||
EnumItemSlot[] aenumitemslot = EnumItemSlot.values();
|
||||
int i = aenumitemslot.length;
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
EnumItemSlot enumitemslot = aenumitemslot[j];
|
||||
ItemStack itemstack = ((EntityLiving) this.tracker).getEquipment(enumitemslot);
|
||||
|
||||
if (!itemstack.isEmpty()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEquipment(this.tracker.getId(), enumitemslot, itemstack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tracker instanceof EntityHuman) {
|
||||
EntityHuman entityhuman = (EntityHuman) this.tracker;
|
||||
|
||||
if (entityhuman.isSleeping()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutBed(entityhuman, new BlockPosition(this.tracker)));
|
||||
}
|
||||
}
|
||||
|
||||
// CraftBukkit start - Fix for nonsensical head yaw
|
||||
this.headYaw = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
|
||||
this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) headYaw));
|
||||
// CraftBukkit end
|
||||
|
||||
if (this.tracker instanceof EntityLiving) {
|
||||
EntityLiving entityliving = (EntityLiving) this.tracker;
|
||||
Iterator iterator = entityliving.getEffects().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
MobEffect mobeffect = (MobEffect) iterator.next();
|
||||
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEffect(this.tracker.getId(), mobeffect));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.tracker.bF().isEmpty()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker));
|
||||
}
|
||||
|
||||
if (this.tracker.isPassenger()) {
|
||||
entityplayer.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker.bJ()));
|
||||
}
|
||||
|
||||
this.tracker.b(entityplayer);
|
||||
entityplayer.d(this.tracker);
|
||||
updatePassengers(entityplayer); // Paper
|
||||
}
|
||||
} else if (this.trackedPlayers.contains(entityplayer)) {
|
||||
this.trackedPlayers.remove(entityplayer);
|
||||
this.tracker.c(entityplayer);
|
||||
entityplayer.c(this.tracker);
|
||||
updatePassengers(entityplayer); // Paper
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean c(EntityPlayer entityplayer) {
|
||||
// Paper start
|
||||
if (tracker.isPassenger()) {
|
||||
return isTrackedBy(tracker.getVehicle(), entityplayer);
|
||||
} else if (hasPassengerInRange(tracker, entityplayer)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isInRangeOfPlayer(entityplayer);
|
||||
}
|
||||
private static boolean hasPassengerInRange(Entity entity, EntityPlayer entityplayer) {
|
||||
if (!entity.isVehicle()) {
|
||||
return false;
|
||||
}
|
||||
for (Entity passenger : entity.passengers) {
|
||||
if (passenger.tracker != null && passenger.tracker.isInRangeOfPlayer(entityplayer)) {
|
||||
return true;
|
||||
}
|
||||
if (passenger.isVehicle()) {
|
||||
if (hasPassengerInRange(passenger, entityplayer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static boolean isTrackedBy(Entity entity, EntityPlayer entityplayer) {
|
||||
return entity == entityplayer || entity.tracker != null && entity.tracker.trackedPlayers.contains(entityplayer);
|
||||
}
|
||||
private void updatePassengers(EntityPlayer player) {
|
||||
if (tracker.isVehicle()) {
|
||||
tracker.passengers.forEach((e) -> {
|
||||
if (e.tracker != null) {
|
||||
e.tracker.updatePlayer(player);
|
||||
}
|
||||
});
|
||||
player.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker));
|
||||
}
|
||||
}
|
||||
private boolean isInRangeOfPlayer(EntityPlayer entityplayer) {
|
||||
// Paper end
|
||||
double d0 = entityplayer.locX - (double) this.xLoc / 4096.0D;
|
||||
double d1 = entityplayer.locZ - (double) this.zLoc / 4096.0D;
|
||||
int i = Math.min(this.e, this.f);
|
||||
|
||||
return d0 >= (double) (-i) && d0 <= (double) i && d1 >= (double) (-i) && d1 <= (double) i && this.tracker.a(entityplayer);
|
||||
}
|
||||
|
||||
private boolean e(EntityPlayer entityplayer) {
|
||||
return entityplayer.x().getPlayerChunkMap().a(entityplayer, this.tracker.ab, this.tracker.ad);
|
||||
}
|
||||
|
||||
public void scanPlayers(List<EntityHuman> list) {
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
this.updatePlayer((EntityPlayer) list.get(i));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Packet<?> e() {
|
||||
if (this.tracker.dead) {
|
||||
// CraftBukkit start - Remove useless error spam, just return
|
||||
// EntityTrackerEntry.d.warn("Fetching addPacket for removed entity");
|
||||
return null;
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
if (this.tracker instanceof EntityPlayer) {
|
||||
return new PacketPlayOutNamedEntitySpawn((EntityHuman) this.tracker);
|
||||
} else if (this.tracker instanceof IAnimal) {
|
||||
this.headYaw = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
|
||||
return new PacketPlayOutSpawnEntityLiving((EntityLiving) this.tracker);
|
||||
} else if (this.tracker instanceof EntityPainting) {
|
||||
return new PacketPlayOutSpawnEntityPainting((EntityPainting) this.tracker);
|
||||
} else if (this.tracker instanceof EntityItem) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 2, 1);
|
||||
} else if (this.tracker instanceof EntityMinecartAbstract) {
|
||||
EntityMinecartAbstract entityminecartabstract = (EntityMinecartAbstract) this.tracker;
|
||||
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 10, entityminecartabstract.v().a());
|
||||
} else if (this.tracker instanceof EntityBoat) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 1);
|
||||
} else if (this.tracker instanceof EntityExperienceOrb) {
|
||||
return new PacketPlayOutSpawnEntityExperienceOrb((EntityExperienceOrb) this.tracker);
|
||||
} else if (this.tracker instanceof EntityFishingHook) {
|
||||
EntityHuman entityhuman = ((EntityFishingHook) this.tracker).l();
|
||||
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 90, entityhuman == null ? this.tracker.getId() : entityhuman.getId());
|
||||
} else {
|
||||
Entity entity;
|
||||
|
||||
if (this.tracker instanceof EntitySpectralArrow) {
|
||||
entity = ((EntitySpectralArrow) this.tracker).shooter;
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 91, 1 + (entity == null ? this.tracker.getId() : entity.getId()));
|
||||
} else if (this.tracker instanceof EntityTippedArrow) {
|
||||
entity = ((EntityArrow) this.tracker).shooter;
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 60, 1 + (entity == null ? this.tracker.getId() : entity.getId()));
|
||||
} else if (this.tracker instanceof EntitySnowball) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 61);
|
||||
} else if (this.tracker instanceof EntityLlamaSpit) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 68);
|
||||
} else if (this.tracker instanceof EntityPotion) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 73);
|
||||
} else if (this.tracker instanceof EntityThrownExpBottle) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 75);
|
||||
} else if (this.tracker instanceof EntityEnderPearl) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 65);
|
||||
} else if (this.tracker instanceof EntityEnderSignal) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 72);
|
||||
} else if (this.tracker instanceof EntityFireworks) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 76);
|
||||
} else if (this.tracker instanceof EntityFireball) {
|
||||
EntityFireball entityfireball = (EntityFireball) this.tracker;
|
||||
PacketPlayOutSpawnEntity packetplayoutspawnentity = null;
|
||||
byte b0 = 63;
|
||||
|
||||
if (this.tracker instanceof EntitySmallFireball) {
|
||||
b0 = 64;
|
||||
} else if (this.tracker instanceof EntityDragonFireball) {
|
||||
b0 = 93;
|
||||
} else if (this.tracker instanceof EntityWitherSkull) {
|
||||
b0 = 66;
|
||||
}
|
||||
|
||||
if (entityfireball.shooter != null) {
|
||||
packetplayoutspawnentity = new PacketPlayOutSpawnEntity(this.tracker, b0, ((EntityFireball) this.tracker).shooter.getId());
|
||||
} else {
|
||||
packetplayoutspawnentity = new PacketPlayOutSpawnEntity(this.tracker, b0, 0);
|
||||
}
|
||||
|
||||
packetplayoutspawnentity.a((int) (entityfireball.dirX * 8000.0D));
|
||||
packetplayoutspawnentity.b((int) (entityfireball.dirY * 8000.0D));
|
||||
packetplayoutspawnentity.c((int) (entityfireball.dirZ * 8000.0D));
|
||||
return packetplayoutspawnentity;
|
||||
} else if (this.tracker instanceof EntityShulkerBullet) {
|
||||
PacketPlayOutSpawnEntity packetplayoutspawnentity1 = new PacketPlayOutSpawnEntity(this.tracker, 67, 0);
|
||||
|
||||
packetplayoutspawnentity1.a((int) (this.tracker.motX * 8000.0D));
|
||||
packetplayoutspawnentity1.b((int) (this.tracker.motY * 8000.0D));
|
||||
packetplayoutspawnentity1.c((int) (this.tracker.motZ * 8000.0D));
|
||||
return packetplayoutspawnentity1;
|
||||
} else if (this.tracker instanceof EntityEgg) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 62);
|
||||
} else if (this.tracker instanceof EntityEvokerFangs) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 79);
|
||||
} else if (this.tracker instanceof EntityTNTPrimed) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 50);
|
||||
} else if (this.tracker instanceof EntityEnderCrystal) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 51);
|
||||
} else if (this.tracker instanceof EntityFallingBlock) {
|
||||
EntityFallingBlock entityfallingblock = (EntityFallingBlock) this.tracker;
|
||||
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 70, Block.getCombinedId(entityfallingblock.getBlock()));
|
||||
} else if (this.tracker instanceof EntityArmorStand) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 78);
|
||||
} else if (this.tracker instanceof EntityItemFrame) {
|
||||
EntityItemFrame entityitemframe = (EntityItemFrame) this.tracker;
|
||||
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 71, entityitemframe.direction.get2DRotationValue(), entityitemframe.getBlockPosition());
|
||||
} else if (this.tracker instanceof EntityLeash) {
|
||||
EntityLeash entityleash = (EntityLeash) this.tracker;
|
||||
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 77, 0, entityleash.getBlockPosition());
|
||||
} else if (this.tracker instanceof EntityAreaEffectCloud) {
|
||||
return new PacketPlayOutSpawnEntity(this.tracker, 3);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Don\'t know how to add " + this.tracker.getClass() + "!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear(EntityPlayer entityplayer) {
|
||||
org.spigotmc.AsyncCatcher.catchOp( "player tracker clear"); // Spigot
|
||||
if (this.trackedPlayers.contains(entityplayer)) {
|
||||
this.trackedPlayers.remove(entityplayer);
|
||||
this.tracker.c(entityplayer);
|
||||
entityplayer.c(this.tracker);
|
||||
updatePassengers(entityplayer); // Paper
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Entity b() {
|
||||
return this.tracker;
|
||||
}
|
||||
|
||||
public void a(int i) {
|
||||
this.f = i;
|
||||
}
|
||||
|
||||
public void c() {
|
||||
this.isMoving = false;
|
||||
}
|
||||
}
|
||||
81
sources/src/main/java/net/minecraft/server/FileIOThread.java
Normal file
81
sources/src/main/java/net/minecraft/server/FileIOThread.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Multi-threaded chunk saving (performance)
|
||||
*/
|
||||
public class FileIOThread implements Runnable {
|
||||
|
||||
private static final FileIOThread a = new FileIOThread();
|
||||
private final List<IAsyncChunkSaver> b = /*Collections.synchronizedList(Lists.newArrayList())*/ null; // Akarin - I don't think any plugin rely on this
|
||||
private volatile long c;
|
||||
private volatile long d;
|
||||
private volatile boolean e;
|
||||
|
||||
private FileIOThread() {
|
||||
// Thread thread = new Thread(this, "File IO Thread"); // Akarin
|
||||
|
||||
// thread.setPriority(1); // Akarin
|
||||
// thread.start(); // Akarin
|
||||
}
|
||||
|
||||
public static FileIOThread a() {
|
||||
return FileIOThread.a;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
this.c();
|
||||
}
|
||||
}
|
||||
|
||||
private void c() {
|
||||
for (int i = 0; i < this.b.size(); ++i) {
|
||||
IAsyncChunkSaver iasyncchunksaver = (IAsyncChunkSaver) this.b.get(i);
|
||||
boolean flag = iasyncchunksaver.a();
|
||||
|
||||
if (!flag) {
|
||||
this.b.remove(i--);
|
||||
++this.d;
|
||||
}
|
||||
|
||||
// Paper start - Add toggle
|
||||
if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) {
|
||||
try {
|
||||
Thread.sleep(this.e ? 0L : 2L);
|
||||
} catch (InterruptedException interruptedexception) {
|
||||
interruptedexception.printStackTrace();
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
|
||||
if (this.b.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(25L);
|
||||
} catch (InterruptedException interruptedexception1) {
|
||||
interruptedexception1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void a(IAsyncChunkSaver iasyncchunksaver) {
|
||||
if (!this.b.contains(iasyncchunksaver)) {
|
||||
++this.c;
|
||||
this.b.add(iasyncchunksaver);
|
||||
}
|
||||
}
|
||||
|
||||
public void b() throws InterruptedException {
|
||||
this.e = true;
|
||||
|
||||
while (this.c != this.d) {
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
|
||||
this.e = false;
|
||||
}
|
||||
}
|
||||
243
sources/src/main/java/net/minecraft/server/ItemMonsterEgg.java
Normal file
243
sources/src/main/java/net/minecraft/server/ItemMonsterEgg.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Restricted spawner modify (feature)
|
||||
*/
|
||||
public class ItemMonsterEgg extends Item {
|
||||
|
||||
public ItemMonsterEgg() {
|
||||
this.b(CreativeModeTab.f);
|
||||
}
|
||||
|
||||
public String b(ItemStack itemstack) {
|
||||
String s = ("" + LocaleI18n.get(this.getName() + ".name")).trim();
|
||||
String s1 = EntityTypes.a(h(itemstack));
|
||||
|
||||
if (s1 != null) {
|
||||
s = s + " " + LocaleI18n.get("entity." + s1 + ".name");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public EnumInteractionResult a(EntityHuman entityhuman, World world, BlockPosition blockposition, EnumHand enumhand, EnumDirection enumdirection, float f, float f1, float f2) {
|
||||
ItemStack itemstack = entityhuman.b(enumhand);
|
||||
|
||||
if (world.isClientSide) {
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
} else if (!entityhuman.a(blockposition.shift(enumdirection), enumdirection, itemstack)) {
|
||||
return EnumInteractionResult.FAIL;
|
||||
} else {
|
||||
IBlockData iblockdata = world.getType(blockposition);
|
||||
Block block = iblockdata.getBlock();
|
||||
|
||||
if (block == Blocks.MOB_SPAWNER && (AkarinGlobalConfig.allowSpawnerModify || entityhuman.isCreativeAndOp())) { // Akarin
|
||||
TileEntity tileentity = world.getTileEntity(blockposition);
|
||||
|
||||
if (tileentity instanceof TileEntityMobSpawner) {
|
||||
MobSpawnerAbstract mobspawnerabstract = ((TileEntityMobSpawner) tileentity).getSpawner();
|
||||
|
||||
mobspawnerabstract.setMobName(h(itemstack));
|
||||
tileentity.update();
|
||||
world.notify(blockposition, iblockdata, iblockdata, 3);
|
||||
if (!entityhuman.abilities.canInstantlyBuild) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
BlockPosition blockposition1 = blockposition.shift(enumdirection);
|
||||
double d0 = this.a(world, blockposition1);
|
||||
Entity entity = a(world, h(itemstack), (double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + d0, (double) blockposition1.getZ() + 0.5D);
|
||||
|
||||
if (entity != null) {
|
||||
if (entity instanceof EntityLiving && itemstack.hasName()) {
|
||||
entity.setCustomName(itemstack.getName());
|
||||
}
|
||||
|
||||
a(world, entityhuman, itemstack, entity);
|
||||
if (!entityhuman.abilities.canInstantlyBuild) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
}
|
||||
|
||||
return EnumInteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
protected double a(World world, BlockPosition blockposition) {
|
||||
AxisAlignedBB axisalignedbb = (new AxisAlignedBB(blockposition)).b(0.0D, -1.0D, 0.0D);
|
||||
List list = world.getCubes((Entity) null, axisalignedbb);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return 0.0D;
|
||||
} else {
|
||||
double d0 = axisalignedbb.b;
|
||||
|
||||
AxisAlignedBB axisalignedbb1;
|
||||
|
||||
for (Iterator iterator = list.iterator(); iterator.hasNext(); d0 = Math.max(axisalignedbb1.e, d0)) {
|
||||
axisalignedbb1 = (AxisAlignedBB) iterator.next();
|
||||
}
|
||||
|
||||
return d0 - (double) blockposition.getY();
|
||||
}
|
||||
}
|
||||
|
||||
public static void a(World world, @Nullable EntityHuman entityhuman, ItemStack itemstack, @Nullable Entity entity) {
|
||||
MinecraftServer minecraftserver = world.getMinecraftServer();
|
||||
|
||||
if (minecraftserver != null && entity != null) {
|
||||
NBTTagCompound nbttagcompound = itemstack.getTag();
|
||||
|
||||
if (nbttagcompound != null && nbttagcompound.hasKeyOfType("EntityTag", 10)) {
|
||||
if (!world.isClientSide && entity.bC() && (entityhuman == null || !minecraftserver.getPlayerList().isOp(entityhuman.getProfile()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
NBTTagCompound nbttagcompound1 = entity.save(new NBTTagCompound());
|
||||
UUID uuid = entity.getUniqueID();
|
||||
|
||||
nbttagcompound1.a(nbttagcompound.getCompound("EntityTag"));
|
||||
entity.a(uuid);
|
||||
entity.f(nbttagcompound1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
Entity entity = a(world, h(itemstack), (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D);
|
||||
|
||||
if (entity == null) {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
} else {
|
||||
if (entity instanceof EntityLiving && itemstack.hasName()) {
|
||||
entity.setCustomName(itemstack.getName());
|
||||
}
|
||||
|
||||
a(world, entityhuman, itemstack, entity);
|
||||
if (!entityhuman.abilities.canInstantlyBuild) {
|
||||
itemstack.subtract(1);
|
||||
}
|
||||
|
||||
entityhuman.b(StatisticList.b((Item) this));
|
||||
return new InteractionResultWrapper(EnumInteractionResult.SUCCESS, itemstack);
|
||||
}
|
||||
} else {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.FAIL, itemstack);
|
||||
}
|
||||
} else {
|
||||
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity a(World world, @Nullable MinecraftKey minecraftkey, double d0, double d1, double d2) {
|
||||
return spawnCreature(world, minecraftkey, d0, d1, d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Entity spawnCreature(World world, @Nullable MinecraftKey minecraftkey, double d0, double d1, double d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
|
||||
if (minecraftkey != null && EntityTypes.eggInfo.containsKey(minecraftkey)) {
|
||||
Entity entity = null;
|
||||
|
||||
for (int i = 0; i < 1; ++i) {
|
||||
entity = EntityTypes.a(minecraftkey, world);
|
||||
if (entity instanceof EntityInsentient) {
|
||||
EntityInsentient entityinsentient = (EntityInsentient) entity;
|
||||
|
||||
entity.setPositionRotation(d0, d1, d2, MathHelper.g(world.random.nextFloat() * 360.0F), 0.0F);
|
||||
entityinsentient.aP = entityinsentient.yaw;
|
||||
entityinsentient.aN = entityinsentient.yaw;
|
||||
entityinsentient.prepare(world.D(new BlockPosition(entityinsentient)), (GroupDataEntity) null);
|
||||
// CraftBukkit start - don't return an entity when CreatureSpawnEvent is canceled
|
||||
if (!world.addEntity(entity, spawnReason)) {
|
||||
entity = null;
|
||||
} else {
|
||||
entityinsentient.D();
|
||||
}
|
||||
// CraftBukkit end
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void a(CreativeModeTab creativemodetab, NonNullList<ItemStack> nonnulllist) {
|
||||
if (this.a(creativemodetab)) {
|
||||
Iterator iterator = EntityTypes.eggInfo.values().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
EntityTypes.MonsterEggInfo entitytypes_monsteregginfo = (EntityTypes.MonsterEggInfo) iterator.next();
|
||||
ItemStack itemstack = new ItemStack(this, 1);
|
||||
|
||||
a(itemstack, entitytypes_monsteregginfo.a);
|
||||
nonnulllist.add(itemstack);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void a(ItemStack itemstack, MinecraftKey minecraftkey) {
|
||||
NBTTagCompound nbttagcompound = itemstack.hasTag() ? itemstack.getTag() : new NBTTagCompound();
|
||||
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
|
||||
|
||||
nbttagcompound1.setString("id", minecraftkey.toString());
|
||||
nbttagcompound.set("EntityTag", nbttagcompound1);
|
||||
itemstack.setTag(nbttagcompound);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static MinecraftKey h(ItemStack itemstack) {
|
||||
NBTTagCompound nbttagcompound = itemstack.getTag();
|
||||
|
||||
if (nbttagcompound == null) {
|
||||
return null;
|
||||
} else if (!nbttagcompound.hasKeyOfType("EntityTag", 10)) {
|
||||
return null;
|
||||
} else {
|
||||
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("EntityTag");
|
||||
|
||||
if (!nbttagcompound1.hasKeyOfType("id", 8)) {
|
||||
return null;
|
||||
} else {
|
||||
String s = nbttagcompound1.getString("id");
|
||||
MinecraftKey minecraftkey = new MinecraftKey(s);
|
||||
|
||||
if (!s.contains(":")) {
|
||||
nbttagcompound1.setString("id", minecraftkey.toString());
|
||||
}
|
||||
|
||||
return minecraftkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,6 @@ 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;
|
||||
@@ -33,7 +32,6 @@ import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add volatile to fields (nsc)
|
||||
* 2) Expose private members (nsc)
|
||||
* 3) Changes lock type to updatable lock (compatibility)
|
||||
* 4) Removes unneed array creation (performance)
|
||||
@@ -82,7 +80,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
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;
|
||||
|
||||
@@ -106,7 +106,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
||||
packetdataserializer.writeBoolean(this.f);
|
||||
packetdataserializer.d(this.c);
|
||||
packetdataserializer.d(this.d.array().length); // Akarin
|
||||
packetdataserializer.writeBytes(this.d.array());
|
||||
packetdataserializer.writeBytes(this.d.array()); // Akarin
|
||||
packetdataserializer.d(this.e.size());
|
||||
Iterator iterator = this.e.iterator();
|
||||
|
||||
|
||||
601
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
601
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
@@ -0,0 +1,601 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import co.aikar.timings.Timing;
|
||||
import com.google.common.base.Predicate;
|
||||
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.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
// CraftBukkit start
|
||||
import java.util.LinkedList;
|
||||
// CraftBukkit end
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Make whole class thread-safe (safety issue)
|
||||
*/
|
||||
@ThreadSafe // Akarin - idk why we need do so!!
|
||||
public class PlayerChunkMap {
|
||||
|
||||
private static final Predicate<EntityPlayer> a = new Predicate() {
|
||||
public boolean a(@Nullable EntityPlayer entityplayer) {
|
||||
return entityplayer != null && !entityplayer.isSpectator();
|
||||
}
|
||||
|
||||
public boolean apply(@Nullable Object object) {
|
||||
return this.a((EntityPlayer) object);
|
||||
}
|
||||
};
|
||||
private static final Predicate<EntityPlayer> b = new Predicate() {
|
||||
public boolean a(@Nullable EntityPlayer entityplayer) {
|
||||
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.x().getGameRules().getBoolean("spectatorsGenerateChunks"));
|
||||
}
|
||||
|
||||
public boolean apply(@Nullable Object object) {
|
||||
return this.a((EntityPlayer) object);
|
||||
}
|
||||
};
|
||||
private final WorldServer world;
|
||||
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
|
||||
private final ReentrantReadWriteLock managedPlayersLock = new ReentrantReadWriteLock(); // Akarin - add lock
|
||||
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<Chunk>() {
|
||||
protected Chunk a() {
|
||||
while (true) {
|
||||
if (iterator.hasNext()) {
|
||||
PlayerChunk playerchunk = (PlayerChunk) iterator.next();
|
||||
Chunk chunk = playerchunk.f();
|
||||
|
||||
if (chunk == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chunk.v() && chunk.isDone()) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
if (!chunk.j()) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
if (!playerchunk.a(128.0D, PlayerChunkMap.a)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return (Chunk) this.endOfData();
|
||||
}
|
||||
}
|
||||
|
||||
protected Chunk computeNext() {
|
||||
return this.a();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized void flush() { // Akarin - synchronized
|
||||
long i = this.world.getTime();
|
||||
int j;
|
||||
PlayerChunk playerchunk;
|
||||
|
||||
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
|
||||
Collections.sort(this.h, new Comparator() {
|
||||
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
|
||||
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
|
||||
}
|
||||
|
||||
public int compare(Object object, Object object1) {
|
||||
return this.a((PlayerChunk) object, (PlayerChunk) object1);
|
||||
}
|
||||
});
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
if (this.m && i % 4L == 2L) {
|
||||
this.m = false;
|
||||
try (Timing ignored = world.timings.doChunkMapSortSendToPlayers.startTiming()) { // Paper
|
||||
Collections.sort(this.g, new Comparator() {
|
||||
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
|
||||
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
|
||||
}
|
||||
|
||||
public int compare(Object object, Object object1) {
|
||||
return this.a((PlayerChunk) object, (PlayerChunk) object1);
|
||||
}
|
||||
});
|
||||
} // Paper timing
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
managedPlayersLock.readLock().lock(); // Akarin
|
||||
if (this.managedPlayers.isEmpty()) {
|
||||
try (Timing ignored = world.timings.doChunkMapUnloadChunks.startTiming()) { // Paper
|
||||
WorldProvider worldprovider = this.world.worldProvider;
|
||||
|
||||
if (!worldprovider.e() && !this.world.savingDisabled) { // Paper - respect saving disabled setting
|
||||
this.world.getChunkProviderServer().b();
|
||||
}
|
||||
} // Paper timing
|
||||
}
|
||||
managedPlayersLock.readLock().unlock(); // Akarin
|
||||
|
||||
}
|
||||
|
||||
public synchronized boolean a(int i, int j) { // Akarin - synchronized
|
||||
long k = d(i, j);
|
||||
|
||||
return this.e.get(k) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized PlayerChunk getChunk(int i, int j) { // Akarin - synchronized
|
||||
return (PlayerChunk) this.e.get(d(i, j));
|
||||
}
|
||||
|
||||
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 (this) { // Akarin - synchronized
|
||||
for (ChunkCoordIntPair pair : chunkList) {
|
||||
this.c(pair.x, pair.z).a(entityplayer);
|
||||
}
|
||||
} // Akarin
|
||||
// CraftBukkit end
|
||||
|
||||
managedPlayersLock.writeLock().lock(); // Akarin
|
||||
this.managedPlayers.add(entityplayer);
|
||||
managedPlayersLock.writeLock().unlock(); // 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
managedPlayersLock.writeLock().lock(); // Akarin
|
||||
this.managedPlayers.remove(entityplayer);
|
||||
managedPlayersLock.writeLock().unlock(); // 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;
|
||||
final int viewDistance = entityplayer.getViewDistance(); // Paper - Player view distance API
|
||||
int i1 = Math.max(getViewDistance(), viewDistance); // 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, viewDistance)) { // Paper - Player view distance API
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entityplayer.d = entityplayer.locX;
|
||||
entityplayer.e = entityplayer.locZ;
|
||||
this.e();
|
||||
|
||||
// CraftBukkit start - send nearest chunks first
|
||||
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
|
||||
synchronized (this) { // Akarin - synchronized
|
||||
for (ChunkCoordIntPair pair : chunksToLoad) {
|
||||
this.c(pair.x, pair.z).a(entityplayer);
|
||||
}
|
||||
} // 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;
|
||||
managedPlayersLock.readLock().lock(); // Akarin
|
||||
ArrayList arraylist = Lists.newArrayList(this.managedPlayers);
|
||||
managedPlayersLock.readLock().unlock(); // 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;
|
||||
|
||||
if (j > 0) {
|
||||
synchronized (this) { // Akarin - synchronized
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Akarin
|
||||
} else {
|
||||
synchronized (this) { // Akarin - synchronized
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Akarin
|
||||
if (markSort) {
|
||||
this.e();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 synchronized void a(PlayerChunk playerchunk) { // Akarin - synchronized
|
||||
// org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Add"); // Paper // Akarin
|
||||
this.f.add(playerchunk);
|
||||
}
|
||||
|
||||
public synchronized void b(PlayerChunk playerchunk) { // Akarin - synchronized
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
@@ -75,12 +75,13 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
|
||||
private final MinecraftServer minecraftServer;
|
||||
public EntityPlayer player;
|
||||
private int e;
|
||||
private volatile long f = getCurrentMillis(); public void setLastPing(long lastPing) { this.f = lastPing;}; public long getLastPing() { return this.f;}; // Paper - OBFHELPER - set ping to delay initial // Akarin - private -> public - volatile
|
||||
private volatile boolean g; public void setPendingPing(boolean isPending) { this.g = isPending;}; public boolean isPendingPing() { return this.g;}; // Paper - OBFHELPER // Akarin - private -> public - volatile
|
||||
private volatile long h; public void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; public long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER // Akarin - private -> public - volatile
|
||||
private long f = getCurrentMillis(); public void setLastPing(long lastPing) { this.f = lastPing;}; public long getLastPing() { return this.f;}; // Paper - OBFHELPER - set ping to delay initial // Akarin - private -> public
|
||||
private boolean g; public void setPendingPing(boolean isPending) { this.g = isPending;}; public boolean isPendingPing() { return this.g;}; // Paper - OBFHELPER // Akarin - private -> public
|
||||
private long h; public void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; public long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER // Akarin - private -> public
|
||||
// CraftBukkit start - multithreaded fields
|
||||
private volatile int chatThrottle;
|
||||
private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle");
|
||||
private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits
|
||||
// CraftBukkit end
|
||||
private int j;
|
||||
private final IntHashMap<Short> k = new IntHashMap();
|
||||
@@ -212,6 +213,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
|
||||
this.minecraftServer.methodProfiler.b();
|
||||
// CraftBukkit start
|
||||
for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ;
|
||||
if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable
|
||||
/* Use thread-safe field access instead
|
||||
if (this.chatThrottle > 0) {
|
||||
--this.chatThrottle;
|
||||
@@ -2293,7 +2295,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
|
||||
// Paper start - async tab completion
|
||||
public void a(PacketPlayInTabComplete packet) {
|
||||
// CraftBukkit start
|
||||
if (chatSpamField.addAndGet(this, 10) > 500 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) {
|
||||
if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { // Paper start - split and make configurable
|
||||
minecraftServer.postToMainThread(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1252,8 +1252,25 @@ public abstract class PlayerList {
|
||||
}
|
||||
|
||||
public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, int i, Packet<?> packet) {
|
||||
for (int j = 0; j < this.players.size(); ++j) {
|
||||
EntityPlayer entityplayer = this.players.get(j);
|
||||
// Paper start - Use world list instead of server list where preferable
|
||||
sendPacketNearby(entityhuman, d0, d1, d2, d3, i, null, packet); // Retained for compatibility
|
||||
}
|
||||
|
||||
public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, WorldServer world, Packet<?> packet) {
|
||||
sendPacketNearby(entityhuman, d0, d1, d2, d3, world.dimension, world, packet);
|
||||
}
|
||||
|
||||
public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, int i, @Nullable WorldServer world, Packet<?> packet) {
|
||||
if (world == null && entityhuman != null && entityhuman.world instanceof WorldServer) {
|
||||
world = (WorldServer) entityhuman.world;
|
||||
}
|
||||
|
||||
List<? extends EntityHuman> players1 = world == null ? players : world.players;
|
||||
for (int j = 0; j < players1.size(); ++j) {
|
||||
EntityHuman entity = players1.get(j);
|
||||
if (!(entity instanceof EntityPlayer)) continue;
|
||||
EntityPlayer entityplayer = (EntityPlayer) players1.get(j);
|
||||
// Paper end
|
||||
|
||||
// CraftBukkit start - Test if player receiving packet can see the source of the packet
|
||||
if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) {
|
||||
@@ -1261,7 +1278,7 @@ public abstract class PlayerList {
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
if (entityplayer != entityhuman && entityplayer.dimension == i) {
|
||||
if (entityplayer != entityhuman && (world != null || entityplayer.dimension == i)) { // Paper
|
||||
double d4 = d0 - entityplayer.locX;
|
||||
double d5 = d1 - entityplayer.locY;
|
||||
double d6 = d2 - entityplayer.locZ;
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.minecraft.server;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Iterator;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -24,7 +26,7 @@ public class RegistryID<K> implements Registry {
|
||||
this.b = (K[]) (new Object[i]);
|
||||
this.c = new int[i];
|
||||
this.d = (K[]) (new Object[i]);
|
||||
this.usedIds = new java.util.BitSet(); // Akarin - 1.13 backport
|
||||
this.usedIds = new BitSet(); // Akarin - 1.13 backport
|
||||
}
|
||||
|
||||
public int getId(@Nullable K k0) {
|
||||
|
||||
3324
sources/src/main/java/net/minecraft/server/World.java
Normal file
3324
sources/src/main/java/net/minecraft/server/World.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,773 +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
|
||||
|
||||
/**
|
||||
* Akarin Changes Note
|
||||
* 1) Add volatile to fields (slack service)
|
||||
*/
|
||||
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 - OBFHELPER: time
|
||||
private volatile long l; // Akarin - volatile - OBFHELPER: 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 volatile EnumDifficulty C; // Akarin - volatile
|
||||
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
|
||||
}
|
||||
@@ -1194,7 +1194,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
|
||||
if (this.entitiesByUUID.containsKey(uuid)) {
|
||||
Entity entity1 = this.entitiesByUUID.get(uuid);
|
||||
|
||||
if (this.f.contains(entity1) || entity1.dead) { // Paper - overwrite the current dead one
|
||||
if (this.f.contains(entity1) || entity1.dead) { // Paper - if dupe is dead, overwrite
|
||||
this.f.remove(entity1);
|
||||
} else {
|
||||
if (!(entity instanceof EntityHuman)) {
|
||||
@@ -1291,7 +1291,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
|
||||
}
|
||||
// CraftBukkit end
|
||||
if (super.strikeLightning(entity)) {
|
||||
this.server.getPlayerList().sendPacketNearby((EntityHuman) null, entity.locX, entity.locY, entity.locZ, 512.0D, dimension, new PacketPlayOutSpawnEntityWeather(entity)); // CraftBukkit - Use dimension
|
||||
this.server.getPlayerList().sendPacketNearby((EntityHuman) null, entity.locX, entity.locY, entity.locZ, 512.0D, this, new PacketPlayOutSpawnEntityWeather(entity)); // CraftBukkit - Use dimension, // Paper - use world instead of dimension
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -1369,8 +1369,8 @@ public class WorldServer extends World implements IAsyncTaskHandler {
|
||||
BlockActionData blockactiondata = (BlockActionData) iterator.next();
|
||||
|
||||
if (this.a(blockactiondata)) {
|
||||
// CraftBukkit - this.worldProvider.dimension -> this.dimension
|
||||
this.server.getPlayerList().sendPacketNearby((EntityHuman) null, blockactiondata.a().getX(), blockactiondata.a().getY(), blockactiondata.a().getZ(), 64.0D, dimension, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c()));
|
||||
// CraftBukkit - this.worldProvider.dimension -> this.dimension, // Paper - dimension -> world
|
||||
this.server.getPlayerList().sendPacketNearby((EntityHuman) null, (double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1922,18 +1922,21 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public void reloadPermissions() {
|
||||
((SimplePluginManager) pluginManager).clearPermissions();
|
||||
loadCustomPermissions();
|
||||
pluginManager.clearPermissions();
|
||||
if (com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions();
|
||||
for (Plugin plugin : pluginManager.getPlugins()) {
|
||||
plugin.getDescription().getPermissions().forEach((perm) -> {
|
||||
for (Permission perm : plugin.getDescription().getPermissions()) {
|
||||
try {
|
||||
pluginManager.addPermission(perm);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!com.destroystokyo.paper.PaperConfig.loadPermsBeforePlugins) loadCustomPermissions();
|
||||
DefaultPermissions.registerCorePermissions();
|
||||
CraftDefaultPermissions.registerCorePermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reloadCommandAliases() {
|
||||
|
||||
@@ -1107,10 +1107,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
||||
EntityTracker tracker = ((WorldServer) entity.world).tracker;
|
||||
// Paper end
|
||||
|
||||
tracker.entriesLock.updateLock().lock(); // Akarin
|
||||
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
|
||||
if (entry != null) {
|
||||
tracker.entriesLock.writeLock().lock(); // Akarin
|
||||
entry.clear(getHandle());
|
||||
tracker.entriesLock.writeLock().unlock(); // Akarin
|
||||
}
|
||||
tracker.entriesLock.updateLock().unlock(); // Akarin
|
||||
|
||||
// Remove the hidden player from this player user list, if they're on it
|
||||
if (other.sentListPacket) {
|
||||
@@ -1157,12 +1161,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
||||
|
||||
getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other));
|
||||
|
||||
tracker.entriesLock.updateLock().lock(); // Akarin
|
||||
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
|
||||
if (entry != null && !entry.trackedPlayers.contains(getHandle())) {
|
||||
tracker.entriesLock.lock(); // Akarin
|
||||
tracker.entriesLock.writeLock().lock(); // Akarin
|
||||
entry.updatePlayer(getHandle());
|
||||
tracker.entriesLock.unlock(); // Akarin
|
||||
tracker.entriesLock.writeLock().unlock(); // Akarin
|
||||
}
|
||||
tracker.entriesLock.updateLock().unlock(); // Akarin
|
||||
}
|
||||
// Paper start
|
||||
private void reregisterPlayer(EntityPlayer player) {
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
"core.MixinCommandKick",
|
||||
"core.MixinCraftServer",
|
||||
"core.MixinWorldServer",
|
||||
"core.MixinFileIOThread",
|
||||
"core.MixinWorldManager",
|
||||
"core.MixinCommandBanIp",
|
||||
"core.MixinChunkSection",
|
||||
"core.MixinAsyncCatcher",
|
||||
"core.MixinTimingHandler",
|
||||
"core.MixinVersionCommand",
|
||||
"core.MixinItemMonsterEgg",
|
||||
"core.MixinMinecraftServer",
|
||||
"core.MixinChunkIOExecutor",
|
||||
"core.MixinPlayerConnectionUtils",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.7.10",
|
||||
"package": "io.akarin.server.mixin",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"server": [
|
||||
"cps.MixinChunk",
|
||||
|
||||
"lighting.MixinChunk",
|
||||
"lighting.MixinWorld",
|
||||
"lighting.MixinWorldServer",
|
||||
"lighting.MixinChunkProviderServer",
|
||||
]
|
||||
}
|
||||
Submodule work/Paper updated: 8b96ee7ea8...f7358c5cf7
Reference in New Issue
Block a user