Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e63df98d43 | ||
|
|
5f440b7fcc | ||
|
|
a4bc2367c2 | ||
|
|
8f42e6bbd2 | ||
|
|
300299196d | ||
|
|
c98b076911 | ||
|
|
0a0c7723da |
No files matched your search
@@ -1,3 +1,4 @@
|
||||
.gradle
|
||||
.idea
|
||||
build
|
||||
run
|
||||
+4
-7
@@ -6,15 +6,12 @@ plugins {
|
||||
subprojects {
|
||||
apply(plugin = "java")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
extensions.configure<JavaPluginExtension>("java") {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"implementation"("com.sk89q.worldedit:worldedit-core:7.2.13")
|
||||
"implementation"("it.unimi.dsi:fastutil:8.5.9")
|
||||
"compileOnly"("org.jetbrains:annotations:23.0.0")
|
||||
repositories {
|
||||
mavenCentral().mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -4,11 +4,12 @@ plugins {
|
||||
|
||||
repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// this is all set up the wrong way around but shadowJar is special
|
||||
implementation("io.papermc.paper:paper-api:1.19.3-R0.1-SNAPSHOT")
|
||||
implementation("dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT")
|
||||
implementation("com.sk89q.worldedit:worldedit-bukkit:7.2.13")
|
||||
shadow(project(":wets-common")) { isTransitive = false }
|
||||
}
|
||||
@@ -20,5 +21,7 @@ tasks {
|
||||
archiveClassifier.set("bukkit")
|
||||
}
|
||||
|
||||
build { dependsOn(shadowJar) }
|
||||
assemble {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ar.emily.wets.bukkit;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class BukkitSchedulerAdapter implements SchedulerAdapter.Instance {
|
||||
|
||||
@Override
|
||||
public Consumer<Runnable> scheduleGlobal(final Plugin plugin, final long initialDelay, final long period) {
|
||||
return task -> plugin.getServer().getScheduler().runTaskTimer(plugin, task, initialDelay, period);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAt(final Plugin plugin, final Location pos, final Runnable task) {
|
||||
final Server server = plugin.getServer();
|
||||
if (server.isPrimaryThread()) {
|
||||
task.run();
|
||||
} else {
|
||||
server.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ar.emily.wets.bukkit;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class FoliaSchedulerAdapter implements SchedulerAdapter.Instance {
|
||||
|
||||
@Override
|
||||
public Consumer<Runnable> scheduleGlobal(final Plugin plugin, final long initialDelay, final long period) {
|
||||
final Server server = plugin.getServer();
|
||||
return task -> server.getGlobalRegionScheduler().runAtFixedRate(plugin, t -> task.run(), initialDelay, period);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAt(final Plugin plugin, final Location pos, final Runnable task) {
|
||||
final Server server = plugin.getServer();
|
||||
if (server.isOwnedByCurrentRegion(pos)) {
|
||||
task.run();
|
||||
} else {
|
||||
server.getRegionScheduler().execute(plugin, pos, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package ar.emily.wets.bukkit;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class SchedulerAdapter {
|
||||
|
||||
private static final Instance INSTANCE = get();
|
||||
|
||||
static Consumer<Runnable> scheduleGlobal(final Plugin plugin, final long initialDelay, final long period) {
|
||||
return INSTANCE.scheduleGlobal(plugin, initialDelay, period);
|
||||
}
|
||||
|
||||
static void runAt(final Plugin plugin, final Location pos, final Runnable task) {
|
||||
INSTANCE.runAt(plugin, pos, task);
|
||||
}
|
||||
|
||||
private static Instance get() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler");
|
||||
return new FoliaSchedulerAdapter();
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
return new BukkitSchedulerAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private SchedulerAdapter() {
|
||||
}
|
||||
|
||||
interface Instance {
|
||||
|
||||
Consumer<Runnable> scheduleGlobal(Plugin plugin, long initialDelay, long period);
|
||||
void runAt(Plugin plugin, Location pos, Runnable task);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,15 @@ public final class WESpreadPlugin extends JavaPlugin implements Listener {
|
||||
|
||||
private static final List<String> COMPLETIONS = List.of("sorted", "not-sorted");
|
||||
|
||||
private final WESpread plugin = new WESpread(new AbstractScheduler(getServer()::getCurrentTick, task -> getServer().getScheduler().runTaskTimer(this, task, 1L, 1L)));
|
||||
private int globalTick = 0;
|
||||
@SuppressWarnings("ThisEscapedInObjectConstruction")
|
||||
private final AbstractScheduler scheduler =
|
||||
new AbstractScheduler(
|
||||
() -> this.globalTick,
|
||||
SchedulerAdapter.scheduleGlobal(this, 1L, 1L),
|
||||
(pos, task) -> SchedulerAdapter.runAt(this, BukkitAdapter.adapt(pos), task)
|
||||
);
|
||||
private final WESpread plugin = new WESpread(this.scheduler);
|
||||
|
||||
@EventHandler
|
||||
public void on(final PlayerQuitEvent event) {
|
||||
@@ -34,14 +42,11 @@ public final class WESpreadPlugin extends JavaPlugin implements Listener {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.scheduler.setup();
|
||||
SchedulerAdapter.scheduleGlobal(this, 0L, 1L).accept(() -> this.globalTick += 1);
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.plugin.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
final @NotNull CommandSender sender,
|
||||
|
||||
@@ -3,6 +3,7 @@ version: ${version}
|
||||
website: https://github.com/emilyy-dev/wets
|
||||
main: ar.emily.wets.bukkit.WESpreadPlugin
|
||||
api-version: 1.13
|
||||
folia-supported: true
|
||||
depend:
|
||||
- WorldEdit
|
||||
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("com.sk89q.worldedit:worldedit-core:7.2.13")
|
||||
api("it.unimi.dsi:fastutil:8.5.9")
|
||||
compileOnlyApi("org.jetbrains:annotations:23.0.0")
|
||||
}
|
||||
@@ -1,20 +1,34 @@
|
||||
package ar.emily.wets.common;
|
||||
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class AbstractScheduler implements Scheduler {
|
||||
public final class AbstractScheduler implements Scheduler {
|
||||
|
||||
private final IntSupplier currentTickGetter;
|
||||
private final Queue<AbstractTask> pendingTasks = new ArrayDeque<>();
|
||||
private final Queue<AbstractTask> tasks = new ArrayDeque<>();
|
||||
private final Consumer<Runnable> globalScheduler;
|
||||
private final BiConsumer<? super Location, ? super Runnable> regionScheduler;
|
||||
|
||||
public AbstractScheduler(final IntSupplier currentTickGetter, final Consumer<Runnable> underlyingScheduler) {
|
||||
public AbstractScheduler(
|
||||
final IntSupplier currentTickGetter,
|
||||
final Consumer<Runnable> globalScheduler,
|
||||
final BiConsumer<? super Location, ? super Runnable> regionScheduler
|
||||
) {
|
||||
this.currentTickGetter = currentTickGetter;
|
||||
underlyingScheduler.accept(() -> {
|
||||
this.globalScheduler = globalScheduler;
|
||||
this.regionScheduler = regionScheduler;
|
||||
}
|
||||
|
||||
public void setup() {
|
||||
this.globalScheduler.accept(() -> {
|
||||
processPendingTasks();
|
||||
processTasks();
|
||||
});
|
||||
@@ -25,6 +39,11 @@ public class AbstractScheduler implements Scheduler {
|
||||
this.pendingTasks.add(new AbstractTask(action, this.currentTickGetter.getAsInt(), initialDelay, period));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAt(final Location pos, final Runnable action) {
|
||||
this.regionScheduler.accept(pos, action);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
this.tasks.addAll(this.pendingTasks);
|
||||
this.pendingTasks.clear();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package ar.emily.wets.common;
|
||||
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface Scheduler {
|
||||
|
||||
void flush();
|
||||
void runPeriodically(Consumer<Task> action, long initialDelay, long period);
|
||||
void runAt(Location pos, Runnable action);
|
||||
|
||||
@FunctionalInterface
|
||||
interface Task {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package ar.emily.wets.common;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.byteArrayViewVarHandle;
|
||||
|
||||
// why am i overcomplicating this
|
||||
final class V5UUID {
|
||||
|
||||
private static final VarHandle AS_LONG_ARRAY = byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
|
||||
private static final MessageDigest MD;
|
||||
|
||||
static {
|
||||
try {
|
||||
MD = MessageDigest.getInstance("SHA-1");
|
||||
} catch (final NoSuchAlgorithmException ex) {
|
||||
// shouldn't throw, SHA-1 is a standard algorithm that is required by Java SE
|
||||
// https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html
|
||||
throw new ExceptionInInitializerError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// MessageDigest isn't thread safe, that's why it's being cloned
|
||||
@SuppressWarnings("AccessToNonThreadSafeStaticField")
|
||||
private static MessageDigest md() {
|
||||
try {
|
||||
return (MessageDigest) MD.clone();
|
||||
} catch (final CloneNotSupportedException ex) {
|
||||
// shouldn't throw
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// use V5 UUID as to not collide with V3 or V4 UUIDs, which are used by the game
|
||||
static UUID create(final byte[] bs) {
|
||||
final var md = md();
|
||||
md.update(bs);
|
||||
final byte[] digest = md.digest();
|
||||
digest[6] &= 0x0f; // clear version
|
||||
digest[6] |= 0x50; // set version 5
|
||||
digest[8] &= 0x3f; // clear variant
|
||||
digest[8] |= (byte) 0x80; // set variant 1
|
||||
return new UUID(
|
||||
(long) AS_LONG_ARRAY.get(digest, 0),
|
||||
(long) AS_LONG_ARRAY.get(digest, 8)
|
||||
);
|
||||
}
|
||||
|
||||
private V5UUID() {
|
||||
}
|
||||
}
|
||||
@@ -3,74 +3,93 @@ package ar.emily.wets.common;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.event.extent.EditSessionEvent;
|
||||
import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extent.AbstractBufferingExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.operation.Operation;
|
||||
import com.sk89q.worldedit.function.operation.RunContext;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.Identifiable;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import com.sk89q.worldedit.util.collection.BlockMap;
|
||||
import com.sk89q.worldedit.util.eventbus.EventHandler;
|
||||
import com.sk89q.worldedit.util.eventbus.Subscribe;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class WESpread {
|
||||
|
||||
private static final Component PLAYERS_ONLY = TextComponent.of("This command can only be ran by players", TextColor.RED);
|
||||
private static final Component INVALID_ARGUMENT = TextComponent.of("Blocks per tick must be a valid positive number", TextColor.RED);
|
||||
public static final UUID NON_PLAYER_ACTOR_ID =
|
||||
V5UUID.create(WESpread.class.descriptorString().getBytes(StandardCharsets.UTF_8));
|
||||
private static final AtomicLongFieldUpdater<WESpread> DEFAULT_BLOCKS_PER_TICK =
|
||||
AtomicLongFieldUpdater.newUpdater(WESpread.class, "defaultBlocksPerTick");
|
||||
|
||||
// @formatter:off
|
||||
private static final Component INVALID_ARGUMENT = TextComponent.of("Blocks per tick must be a valid positive number");
|
||||
private static final Component COMMAND_USAGE = TextComponent.of("Usage: /wets (<blocks per tick> | sorted | not-sorted)");
|
||||
private static final Component COMMAND_SORTED = TextComponent.of("Block placement of new operations will now be sorted");
|
||||
private static final Component COMMAND_NOT_SORTED = TextComponent.of("Block placement of new operations will now be unsorted");
|
||||
private static final Component COMMAND_BPT_UPDATED = TextComponent.of("Blocks per tick placement updated");
|
||||
// @formatter:on
|
||||
|
||||
private final Scheduler scheduler;
|
||||
private final Object2LongMap<UUID> blocksPerTickMap = new Object2LongOpenHashMap<>();
|
||||
private final Set<UUID> actorsWhosePlacementIsNotSorted = new HashSet<>();
|
||||
private long defaultBlocksPerTick = 1;
|
||||
// Folia note: none of the ops on these collections are critical enough to be atomic
|
||||
private final ConcurrentMap<UUID, Long> blocksPerTickMap = new ConcurrentHashMap<>();
|
||||
private final Set<UUID> actorsWhosePlacementIsNotSorted = ConcurrentHashMap.newKeySet();
|
||||
private volatile long defaultBlocksPerTick = 1;
|
||||
|
||||
public WESpread(final Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public void playerLogout(final UUID id) {
|
||||
this.blocksPerTickMap.removeLong(id);
|
||||
this.blocksPerTickMap.remove(id);
|
||||
this.actorsWhosePlacementIsNotSorted.remove(id);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
this.defaultBlocksPerTick = Long.MAX_VALUE;
|
||||
public void load() {
|
||||
WorldEdit.getInstance().getEventBus().register(this);
|
||||
}
|
||||
|
||||
// we really really really want to flush the queue before worldedit itself unloads
|
||||
@Subscribe(priority = EventHandler.Priority.VERY_EARLY)
|
||||
public void on(final PlatformUnreadyEvent event) {
|
||||
WorldEdit.getInstance().getEventBus().unregister(this);
|
||||
DEFAULT_BLOCKS_PER_TICK.set(this, Long.MAX_VALUE);
|
||||
this.blocksPerTickMap.clear();
|
||||
this.actorsWhosePlacementIsNotSorted.clear();
|
||||
this.scheduler.flush();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
WorldEdit.getInstance().getEventBus().register(new Object() {
|
||||
@Subscribe
|
||||
public void on(final EditSessionEvent event) {
|
||||
final @Nullable Actor actor = event.getActor();
|
||||
if (event.getStage() == EditSession.Stage.BEFORE_CHANGE && actor != null) {
|
||||
event.setExtent(new SchedulingExtent(event.getExtent(), actor.getUniqueId()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@Subscribe
|
||||
public void on(final EditSessionEvent event) {
|
||||
if (event.getStage() == EditSession.Stage.BEFORE_CHANGE) {
|
||||
final UUID actorId =
|
||||
Optional.ofNullable(event.getActor())
|
||||
// use a set ID for non-player actors, such as console or potentially API users
|
||||
.filter(Actor::isPlayer)
|
||||
.map(Identifiable::getUniqueId)
|
||||
.orElse(NON_PLAYER_ACTOR_ID);
|
||||
event.setExtent(new SchedulingExtent(event.getExtent(), actorId));
|
||||
}
|
||||
}
|
||||
|
||||
public void command(final Actor source, final List<String> args) {
|
||||
@@ -78,18 +97,15 @@ public final class WESpread {
|
||||
source.print(COMMAND_USAGE);
|
||||
return;
|
||||
}
|
||||
if (!(source instanceof Player player)) {
|
||||
source.print(PLAYERS_ONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID id = source.isPlayer() ? source.getUniqueId() : NON_PLAYER_ACTOR_ID;
|
||||
final String arg = args.iterator().next();
|
||||
if ("sorted".equals(arg)) {
|
||||
this.actorsWhosePlacementIsNotSorted.remove(player.getUniqueId());
|
||||
this.actorsWhosePlacementIsNotSorted.remove(id);
|
||||
source.print(COMMAND_SORTED);
|
||||
return;
|
||||
} else if ("not-sorted".equals(arg)) {
|
||||
this.actorsWhosePlacementIsNotSorted.add(player.getUniqueId());
|
||||
this.actorsWhosePlacementIsNotSorted.add(id);
|
||||
source.print(COMMAND_NOT_SORTED);
|
||||
return;
|
||||
}
|
||||
@@ -97,10 +113,10 @@ public final class WESpread {
|
||||
try {
|
||||
long blocksPerTick = Long.parseLong(arg);
|
||||
if (blocksPerTick < 0) { blocksPerTick = Long.MAX_VALUE; }
|
||||
this.blocksPerTickMap.put(player.getUniqueId(), blocksPerTick);
|
||||
this.blocksPerTickMap.put(id, blocksPerTick);
|
||||
source.print(COMMAND_BPT_UPDATED);
|
||||
} catch (final NumberFormatException exception) {
|
||||
source.print(INVALID_ARGUMENT);
|
||||
source.printError(INVALID_ARGUMENT);
|
||||
source.print(COMMAND_USAGE);
|
||||
}
|
||||
}
|
||||
@@ -111,10 +127,11 @@ public final class WESpread {
|
||||
private final LongSupplier blocksPerTick;
|
||||
private final boolean sorted;
|
||||
|
||||
private SchedulingExtent(final Extent delegate, final UUID playerId) {
|
||||
private SchedulingExtent(final Extent delegate, final UUID actor) {
|
||||
super(delegate);
|
||||
this.sorted = !WESpread.this.actorsWhosePlacementIsNotSorted.contains(playerId);
|
||||
this.blocksPerTick = () -> WESpread.this.blocksPerTickMap.getOrDefault(playerId, WESpread.this.defaultBlocksPerTick);
|
||||
this.sorted = !WESpread.this.actorsWhosePlacementIsNotSorted.contains(actor);
|
||||
this.blocksPerTick =
|
||||
() -> WESpread.this.blocksPerTickMap.getOrDefault(actor, DEFAULT_BLOCKS_PER_TICK.get(WESpread.this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,8 +160,7 @@ public final class WESpread {
|
||||
|
||||
@Override
|
||||
public Operation resume(final RunContext run) {
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (this.started == false && it.hasNext()) {
|
||||
if (!this.started && it.hasNext()) {
|
||||
this.started = true;
|
||||
WESpread.this.scheduler.runPeriodically(this::putBlock, 1L, 1L);
|
||||
}
|
||||
@@ -156,10 +172,17 @@ public final class WESpread {
|
||||
try {
|
||||
for (long i = 0; it.hasNext() && i < SchedulingExtent.this.blocksPerTick.getAsLong(); ++i) {
|
||||
final var entry = it.next();
|
||||
setDelegateBlock(entry.getKey(), entry.getValue());
|
||||
WESpread.this.scheduler.runAt(
|
||||
new Location(getExtent(), entry.getKey().toVector3()),
|
||||
() -> {
|
||||
try {
|
||||
setDelegateBlock(entry.getKey(), entry.getValue());
|
||||
} catch (final WorldEditException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (final WorldEditException exception) {
|
||||
throw new RuntimeException(exception.getMessage(), exception);
|
||||
} finally {
|
||||
if (!it.hasNext()) { task.cancel(); }
|
||||
}
|
||||
|
||||
+10
-1
@@ -3,7 +3,8 @@ plugins {
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots/").mavenContent { snapshotsOnly() }
|
||||
maven("https://maven.enginehub.org/repo/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -26,4 +27,12 @@ tasks {
|
||||
archiveBaseName.set("WETS")
|
||||
archiveClassifier.set("fabric")
|
||||
}
|
||||
|
||||
assemble {
|
||||
dependsOn(remapJar)
|
||||
}
|
||||
|
||||
runClient {
|
||||
javaLauncher.set(project.javaToolchains.launcherFor(project.java.toolchain))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package ar.emily.wets.fabric;
|
||||
|
||||
import ar.emily.wets.common.WESpread;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.AbstractNonPlayerActor;
|
||||
import com.sk89q.worldedit.session.SessionKey;
|
||||
import com.sk89q.worldedit.util.formatting.WorldEditText;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer;
|
||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
final class FabricNonPlayerActor extends AbstractNonPlayerActor {
|
||||
|
||||
private static final String[] EMPTY_STRINGS = new String[0];
|
||||
|
||||
private final CommandSourceStack source;
|
||||
|
||||
FabricNonPlayerActor(final CommandSourceStack source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return WESpread.NON_PLAYER_ACTOR_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.source.getTextName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void printRaw(final String msg) {
|
||||
for (final String part : msg.split("\n")) {
|
||||
this.source.sendSystemMessage(net.minecraft.network.chat.Component.literal(part));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void printDebug(final String msg) {
|
||||
for (final String part : msg.split("\n")) {
|
||||
this.source.sendSystemMessage(net.minecraft.network.chat.Component.literal(part).withStyle(ChatFormatting.GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void print(final String msg) {
|
||||
for (final String part : msg.split("\n")) {
|
||||
this.source.sendSystemMessage(
|
||||
net.minecraft.network.chat.Component.literal(part).withStyle(ChatFormatting.LIGHT_PURPLE)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void printError(final String msg) {
|
||||
for (final String part : msg.split("\n")) {
|
||||
this.source.sendSystemMessage(net.minecraft.network.chat.Component.literal(part).withStyle(ChatFormatting.RED));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public void print(final Component component) {
|
||||
this.source.sendSystemMessage(
|
||||
net.minecraft.network.chat.Component.Serializer.fromJson(
|
||||
GsonComponentSerializer.INSTANCE.serialize(WorldEditText.format(component, getLocale()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return WorldEdit.getInstance().getConfiguration().defaultLocale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionKey getSessionKey() {
|
||||
return new SessionKey() {
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return FabricNonPlayerActor.this.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return FabricNonPlayerActor.this.getUniqueId();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getGroups() {
|
||||
return EMPTY_STRINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermission(final String permission) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(final String permission) {
|
||||
return Permissions.check(this.source, permission);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.fabric.FabricAdapter;
|
||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
@@ -30,37 +31,51 @@ public final class WESpreadMod implements ModInitializer {
|
||||
private static final List<String> SORTED = List.of("sorted");
|
||||
private static final List<String> NOT_SORTED = List.of("not-sorted");
|
||||
|
||||
private static Actor adaptActor(final CommandSourceStack source) throws CommandSyntaxException {
|
||||
return source.isPlayer() ?
|
||||
FabricAdapter.adaptPlayer(source.getPlayerOrException()) :
|
||||
new FabricNonPlayerActor(source);
|
||||
}
|
||||
|
||||
private WESpread plugin;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> command(dispatcher));
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
||||
this.plugin = new WESpread(new AbstractScheduler(server::getTickCount, task -> ServerTickEvents.END_SERVER_TICK.register(s -> task.run())));
|
||||
final AbstractScheduler scheduler =
|
||||
new AbstractScheduler(
|
||||
server::getTickCount,
|
||||
task -> ServerTickEvents.END_SERVER_TICK.register(s -> task.run()),
|
||||
(pos, task) -> task.run() // already on server tick
|
||||
);
|
||||
scheduler.setup();
|
||||
this.plugin = new WESpread(scheduler);
|
||||
this.plugin.load();
|
||||
});
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register(server -> this.plugin.flush());
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((packetListener, server) ->
|
||||
this.plugin.playerLogout(packetListener.getPlayer().getUUID())
|
||||
);
|
||||
}
|
||||
|
||||
private int executeCommand(final CommandContext<CommandSourceStack> ctx, final List<String> args) throws CommandSyntaxException {
|
||||
this.plugin.command(FabricAdapter.adaptPlayer(ctx.getSource().getPlayerOrException()), args);
|
||||
private int executeCommand(final CommandContext<? extends CommandSourceStack> ctx, final List<String> args)
|
||||
throws CommandSyntaxException {
|
||||
this.plugin.command(adaptActor(ctx.getSource()), args);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private void command(final CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
final Predicate<CommandSourceStack> permissionRequirement = Permissions.require("wets", LEVEL_GAMEMASTERS);
|
||||
final LiteralCommandNode<CommandSourceStack> wetsCommand = dispatcher.register(
|
||||
literal("worldedit-tick-spreader")
|
||||
.requires(permissionRequirement)
|
||||
.then(literal("sorted").executes(ctx -> executeCommand(ctx, SORTED)))
|
||||
.then(literal("not-sorted").executes(ctx -> executeCommand(ctx, NOT_SORTED)))
|
||||
.then(argument("blocks-per-tick", longArg()).executes(ctx ->
|
||||
executeCommand(ctx, List.of(String.valueOf(getLong(ctx, "blocks-per-tick"))))
|
||||
))
|
||||
);
|
||||
final LiteralCommandNode<CommandSourceStack> wetsCommand =
|
||||
dispatcher.register(
|
||||
literal("worldedit-tick-spreader")
|
||||
.requires(permissionRequirement)
|
||||
.then(literal("sorted").executes(ctx -> executeCommand(ctx, SORTED)))
|
||||
.then(literal("not-sorted").executes(ctx -> executeCommand(ctx, NOT_SORTED)))
|
||||
.then(argument("blocks-per-tick", longArg()).executes(ctx ->
|
||||
executeCommand(ctx, List.of(String.valueOf(getLong(ctx, "blocks-per-tick"))))
|
||||
))
|
||||
);
|
||||
|
||||
dispatcher.register(literal("wets").requires(permissionRequirement).redirect(wetsCommand));
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
org.gradle.caching=false
|
||||
group=ar.emily
|
||||
version=0.0.2
|
||||
version=0.0.4-SNAPSHOT
|
||||
Reference in new issue
Block a user