mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-29 20:09:17 +00:00
[ci skip] cleanup
This commit is contained in:
@@ -18,12 +18,15 @@ import java.util.function.Consumer;
|
||||
|
||||
// Original project: https://github.com/thebrightspark/AsyncLocator
|
||||
public class AsyncLocator {
|
||||
|
||||
private static final ExecutorService LOCATING_EXECUTOR_SERVICE;
|
||||
|
||||
private AsyncLocator() {}
|
||||
private AsyncLocator() {
|
||||
}
|
||||
|
||||
public static class AsyncLocatorThread extends TickThread {
|
||||
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
|
||||
|
||||
public AsyncLocatorThread(Runnable run, String name) {
|
||||
super(run, name, THREAD_COUNTER.incrementAndGet());
|
||||
}
|
||||
@@ -37,23 +40,23 @@ public class AsyncLocator {
|
||||
static {
|
||||
int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads;
|
||||
LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor(
|
||||
1,
|
||||
threads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("Leaf Async Locator Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
1,
|
||||
threads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("Leaf Async Locator Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,15 +71,15 @@ public class AsyncLocator {
|
||||
* and returns a {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<BlockPos> locate(
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
|
||||
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
@@ -87,41 +90,41 @@ public class AsyncLocator {
|
||||
* {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<Pair<BlockPos, Holder<Structure>>> locate(
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
|
||||
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
private static void doLocateLevel(
|
||||
CompletableFuture<BlockPos> completableFuture,
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
CompletableFuture<BlockPos> completableFuture,
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPos);
|
||||
}
|
||||
|
||||
private static void doLocateChunkGenerator(
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
Pair<BlockPos, Holder<Structure>> foundPair = level.getChunkSource().getGenerator()
|
||||
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
|
||||
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPair);
|
||||
}
|
||||
|
||||
@@ -161,4 +164,4 @@ public class AsyncLocator {
|
||||
completableFuture.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class AsyncPath extends Path {
|
||||
*/
|
||||
public synchronized void process() {
|
||||
if (this.processState == PathProcessState.COMPLETED ||
|
||||
this.processState == PathProcessState.PROCESSING) {
|
||||
this.processState == PathProcessState.PROCESSING) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public class AsyncPath extends Path {
|
||||
*/
|
||||
private void checkProcessed() {
|
||||
if (this.processState == PathProcessState.WAITING ||
|
||||
this.processState == PathProcessState.PROCESSING) { // Block if we are on processing
|
||||
this.processState == PathProcessState.PROCESSING) { // Block if we are on processing
|
||||
this.process();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ import java.util.function.Consumer;
|
||||
public class AsyncPathProcessor {
|
||||
|
||||
private static final Executor pathProcessingExecutor = new ThreadPoolExecutor(
|
||||
1,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("Leaf Async Pathfinding Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
1,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("Leaf Async Pathfinding Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
);
|
||||
|
||||
protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
|
||||
@@ -42,7 +42,7 @@ public class AsyncPathProcessor {
|
||||
public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) {
|
||||
if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) {
|
||||
asyncPath.postProcessing(() ->
|
||||
MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path))
|
||||
MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path))
|
||||
);
|
||||
} else {
|
||||
afterProcessing.accept(path);
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class NodeEvaluatorCache {
|
||||
|
||||
private static final Map<NodeEvaluatorFeatures, MultiThreadedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>();
|
||||
private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
|
||||
|
||||
public record NodeEvaluatorFeatures(
|
||||
NodeEvaluatorType type,
|
||||
boolean canPassDoors,
|
||||
boolean canFloat,
|
||||
boolean canWalkOverFences,
|
||||
boolean canOpenDoors,
|
||||
boolean allowBreaching
|
||||
NodeEvaluatorType type,
|
||||
boolean canPassDoors,
|
||||
boolean canFloat,
|
||||
boolean canWalkOverFences,
|
||||
boolean canOpenDoors,
|
||||
boolean allowBreaching
|
||||
) {
|
||||
public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) {
|
||||
NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator);
|
||||
|
||||
@@ -4,8 +4,5 @@ import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface NodeEvaluatorGenerator {
|
||||
|
||||
@NotNull
|
||||
NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures);
|
||||
|
||||
}
|
||||
@NotNull NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
|
||||
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
@@ -67,7 +66,7 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
});
|
||||
@@ -89,7 +88,7 @@ public class MultithreadedTracker {
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
}
|
||||
|
||||
|
||||
@@ -198,9 +198,9 @@ public class LeafConfig {
|
||||
|
||||
private static List<String> buildSparkExtraConfigs() {
|
||||
List<String> extraConfigs = new ArrayList<>(Arrays.asList(
|
||||
"config/leaf-global.yml",
|
||||
"config/gale-global.yml",
|
||||
"config/gale-world-defaults.yml"
|
||||
"config/leaf-global.yml",
|
||||
"config/gale-global.yml",
|
||||
"config/gale-world-defaults.yml"
|
||||
));
|
||||
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
@@ -212,7 +212,7 @@ public class LeafConfig {
|
||||
|
||||
private static String[] buildSparkHiddenPaths() {
|
||||
return new String[]{
|
||||
SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key
|
||||
SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@ public class LeafGlobalConfig {
|
||||
protected static final boolean isCN = CURRENT_REGION.equals("CN");
|
||||
|
||||
public LeafGlobalConfig(boolean init) throws Exception {
|
||||
configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE));
|
||||
configFile.set("config-version", 3.0);
|
||||
configFile.addComments("config-version", pickStringRegionBased("""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
Discord: https://discord.com/invite/gfgAwdSEuM""",
|
||||
"""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
QQ Group: 619278377"""));
|
||||
configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE));
|
||||
configFile.set("config-version", 3.0);
|
||||
configFile.addComments("config-version", pickStringRegionBased("""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
Discord: https://discord.com/invite/gfgAwdSEuM""",
|
||||
"""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
QQ Group: 619278377"""));
|
||||
|
||||
// Pre-structure to force order
|
||||
structureConfig();
|
||||
// Pre-structure to force order
|
||||
structureConfig();
|
||||
}
|
||||
|
||||
protected void structureConfig() {
|
||||
|
||||
@@ -42,7 +42,8 @@ public class LagCompensation {
|
||||
public static final int MAX_TPS = 20;
|
||||
public static final int FULL_TICK = 50;
|
||||
|
||||
private TPSCalculator() {}
|
||||
private TPSCalculator() {
|
||||
}
|
||||
|
||||
public static void onTick() {
|
||||
if (currentTick != null) {
|
||||
@@ -111,4 +112,4 @@ public class LagCompensation {
|
||||
allMissedTicks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class PositionalBiomeGetter implements Supplier<Holder<Biome>> {
|
||||
|
||||
private final Function<BlockPos, Holder<Biome>> biomeGetter;
|
||||
private final BlockPos.MutableBlockPos pos;
|
||||
private int nextX, nextY, nextZ;
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.BitSet;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class CachedOrNewBitsGetter {
|
||||
|
||||
private static final IntFunction<BitSet> bitSetConstructor = BitSet::new;
|
||||
|
||||
public static ThreadLocal<Int2ObjectOpenHashMap<BitSet>> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new);
|
||||
|
||||
@@ -2,17 +2,20 @@ package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
* @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000
|
||||
*/
|
||||
public class IterateOutwardsCache {
|
||||
|
||||
//POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache
|
||||
public static final BlockPos POS_ZERO = new BlockPos(0,0,0);
|
||||
public static final BlockPos POS_ZERO = new BlockPos(0, 0, 0);
|
||||
|
||||
|
||||
private final ConcurrentHashMap<Long, LongArrayList> table;
|
||||
@@ -68,4 +71,4 @@ public class IterateOutwardsCache {
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
@@ -22,7 +24,7 @@ public class LongList2BlockPosMutableIterable implements Iterable<BlockPos> {
|
||||
|
||||
@Override
|
||||
public Iterator<BlockPos> iterator() {
|
||||
return new Iterator<BlockPos>() {
|
||||
return new Iterator<>() {
|
||||
|
||||
private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator();
|
||||
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
@@ -36,11 +38,10 @@ public class LongList2BlockPosMutableIterable implements Iterable<BlockPos> {
|
||||
public net.minecraft.core.BlockPos next() {
|
||||
long nextPos = this.it.nextLong();
|
||||
return this.pos.set(
|
||||
LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos));
|
||||
LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.function.Function;
|
||||
* Backed by an {@link Object2ObjectOpenHashMap}, with string keys interned to save memory.
|
||||
*/
|
||||
public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<String, T> {
|
||||
|
||||
private static final Interner<String> KEY_INTERNER = Interner.newWeakInterner();
|
||||
|
||||
private static String intern(String key) {
|
||||
@@ -49,10 +50,12 @@ public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<Str
|
||||
public static <T> StringCanonizingOpenHashMap<T> deepCopy(StringCanonizingOpenHashMap<T> incomingMap, Function<T, T> deepCopier) {
|
||||
StringCanonizingOpenHashMap<T> newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f);
|
||||
ObjectIterator<Entry<String, T>> iterator = incomingMap.object2ObjectEntrySet().fastIterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, T> entry = iterator.next();
|
||||
newMap.putWithoutInterning(entry.getKey(), deepCopier.apply(entry.getValue()));
|
||||
}
|
||||
|
||||
return newMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ import net.minecraft.util.Mth;
|
||||
* from {@link Mth}. Validation is performed during runtime to ensure that the table is correct.
|
||||
*
|
||||
* @author coderbot16 Author of the original (and very clever) implementation in Rust:
|
||||
* https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src
|
||||
* <a href="https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src"></a>
|
||||
* @author jellysquid3 Additional optimizations, port to Java
|
||||
*/
|
||||
public class CompactSineLUT {
|
||||
|
||||
private static final int[] SINE_TABLE_INT = new int[16384 + 1];
|
||||
private static final float SINE_TABLE_MIDPOINT;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.random.RandomGeneratorFactory;
|
||||
|
||||
|
||||
public class FasterRandomSource implements BitRandomSource {
|
||||
|
||||
private static final int INT_BITS = 48;
|
||||
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
@@ -75,7 +76,7 @@ public class FasterRandomSource implements BitRandomSource {
|
||||
@Override
|
||||
public RandomSource fromHashOf(String seed) {
|
||||
int i = seed.hashCode();
|
||||
return new FasterRandomSource((long)i ^ this.seed);
|
||||
return new FasterRandomSource((long) i ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,12 @@ public class LeafVersionFetcher extends AbstractPaperVersionFetcher {
|
||||
|
||||
public LeafVersionFetcher() {
|
||||
super(
|
||||
"ver/1.21.3",
|
||||
"https://github.com/Winds-Studio/Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf"
|
||||
"ver/1.21.3",
|
||||
"https://github.com/Winds-Studio/Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user