mirror of
https://github.com/LeavesMC/Leaves.git
synced 2025-12-19 14:59:32 +00:00
Fix replay api and add null check in botlist (#627)
* Fix replay api and add null check in botlist * Update botlist fix * Fix bugs * All done! * Clean up
This commit is contained in:
@@ -36,6 +36,7 @@ import org.leavesmc.leaves.event.bot.BotRemoveEvent;
|
|||||||
import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent;
|
import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -204,7 +205,7 @@ public class BotList {
|
|||||||
playerIO.save(bot);
|
playerIO.save(bot);
|
||||||
} else {
|
} else {
|
||||||
bot.dropAll(true);
|
bot.dropAll(true);
|
||||||
botsNameByWorldUuid.get(bot.level().uuid.toString()).remove(bot.getBukkitEntity().getRealName());
|
botsNameByWorldUuid.getOrDefault(bot.level().uuid.toString(), new HashSet<>()).remove(bot.getBukkitEntity().getRealName());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot.isPassenger() && event.shouldSave()) {
|
if (bot.isPassenger() && event.shouldSave()) {
|
||||||
@@ -305,6 +306,17 @@ public class BotList {
|
|||||||
bots.forEach(this::loadNewBot);
|
bots.forEach(this::loadNewBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateBotLevel(ServerBot bot, ServerLevel level) {
|
||||||
|
String prevUuid = bot.level().uuid.toString();
|
||||||
|
String newUuid = level.uuid.toString();
|
||||||
|
this.botsNameByWorldUuid
|
||||||
|
.computeIfAbsent(newUuid, (k) -> new HashSet<>())
|
||||||
|
.add(bot.getBukkitEntity().getRealName());
|
||||||
|
this.botsNameByWorldUuid
|
||||||
|
.computeIfAbsent(prevUuid, (k) -> new HashSet<>())
|
||||||
|
.remove(bot.getBukkitEntity().getRealName());
|
||||||
|
}
|
||||||
|
|
||||||
public void networkTick() {
|
public void networkTick() {
|
||||||
this.bots.forEach(ServerBot::networkTick);
|
this.bots.forEach(ServerBot::networkTick);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,6 +275,12 @@ public class ServerBot extends ServerPlayer {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServerLevel(ServerLevel level) {
|
||||||
|
BotList.INSTANCE.updateBotLevel(this, level);
|
||||||
|
super.setServerLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void knockback(double strength, double x, double z, @Nullable Entity attacker, EntityKnockbackEvent.@NotNull Cause eventCause) {
|
public void knockback(double strength, double x, double z, @Nullable Entity attacker, EntityKnockbackEvent.@NotNull Cause eventCause) {
|
||||||
if (!this.hurtMarked) {
|
if (!this.hurtMarked) {
|
||||||
|
|||||||
@@ -26,14 +26,17 @@ import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeat
|
|||||||
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
|
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
|
||||||
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
|
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
|
||||||
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
|
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
|
||||||
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
|
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
|
||||||
import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
|
import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundTrackedWaypointPacket;
|
||||||
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
|
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.RegistryLayer;
|
import net.minecraft.server.RegistryLayer;
|
||||||
import net.minecraft.server.packs.repository.KnownPack;
|
import net.minecraft.server.packs.repository.KnownPack;
|
||||||
import net.minecraft.tags.TagNetworkSerialization;
|
import net.minecraft.tags.TagNetworkSerialization;
|
||||||
import net.minecraft.world.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.PositionMoveRotation;
|
||||||
import net.minecraft.world.flag.FeatureFlags;
|
import net.minecraft.world.flag.FeatureFlags;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -41,23 +44,27 @@ import org.leavesmc.leaves.LeavesLogger;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class Recorder extends Connection {
|
public class Recorder extends Connection {
|
||||||
|
|
||||||
public static final Executor saveService = Executors.newVirtualThreadPerTaskExecutor();
|
|
||||||
public static final LeavesLogger LOGGER = LeavesLogger.LOGGER;
|
public static final LeavesLogger LOGGER = LeavesLogger.LOGGER;
|
||||||
|
public final ExecutorService saveService = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private final ReplayFile replayFile;
|
private final ReplayFile replayFile;
|
||||||
private final ServerPhotographer photographer;
|
private final ServerPhotographer photographer;
|
||||||
private final RecorderOption recorderOption;
|
private final RecorderOption recorderOption;
|
||||||
private final RecordMetaData metaData;
|
private final RecordMetaData metaData;
|
||||||
|
private final AtomicBoolean isSaving = new AtomicBoolean(false);
|
||||||
|
|
||||||
private boolean stopped = false;
|
private boolean stopped = false;
|
||||||
private boolean paused = false;
|
private boolean paused = false;
|
||||||
@@ -68,7 +75,6 @@ public class Recorder extends Connection {
|
|||||||
private long timeShift = 0;
|
private long timeShift = 0;
|
||||||
|
|
||||||
private boolean isSaved;
|
private boolean isSaved;
|
||||||
private boolean isSaving;
|
|
||||||
private ConnectionProtocol state = ConnectionProtocol.LOGIN;
|
private ConnectionProtocol state = ConnectionProtocol.LOGIN;
|
||||||
|
|
||||||
public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException {
|
public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException {
|
||||||
@@ -77,7 +83,7 @@ public class Recorder extends Connection {
|
|||||||
this.photographer = photographer;
|
this.photographer = photographer;
|
||||||
this.recorderOption = recorderOption;
|
this.recorderOption = recorderOption;
|
||||||
this.metaData = new RecordMetaData();
|
this.metaData = new RecordMetaData();
|
||||||
this.replayFile = new ReplayFile(replayFile);
|
this.replayFile = new ReplayFile(replayFile, saveService);
|
||||||
this.channel = new LocalChannel();
|
this.channel = new LocalChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +99,8 @@ public class Recorder extends Connection {
|
|||||||
this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN);
|
this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN);
|
||||||
this.startConfiguration();
|
this.startConfiguration();
|
||||||
|
|
||||||
|
savePacket(ClientboundPlayerPositionPacket.of(photographer.getId(), PositionMoveRotation.of(photographer), Collections.emptySet()));
|
||||||
|
|
||||||
if (recorderOption.forceWeather != null) {
|
if (recorderOption.forceWeather != null) {
|
||||||
setWeather(recorderOption.forceWeather);
|
setWeather(recorderOption.forceWeather);
|
||||||
}
|
}
|
||||||
@@ -170,40 +178,46 @@ public class Recorder extends Connection {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(@NotNull Packet<?> packet, @Nullable ChannelFutureListener callbacks, boolean flush) {
|
public void send(@NotNull Packet<?> packet, @Nullable ChannelFutureListener callbacks, boolean flush) {
|
||||||
if (!stopped) {
|
if (stopped) {
|
||||||
if (packet instanceof BundlePacket<?> packet1) {
|
return;
|
||||||
|
}
|
||||||
|
switch (packet) {
|
||||||
|
case BundlePacket<?> packet1 -> {
|
||||||
packet1.subPackets().forEach(subPacket -> send(subPacket, null));
|
packet1.subPackets().forEach(subPacket -> send(subPacket, null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case ClientboundAddEntityPacket packet1 -> {
|
||||||
if (packet instanceof ClientboundAddEntityPacket packet1) {
|
|
||||||
if (packet1.getType() == EntityType.PLAYER) {
|
if (packet1.getType() == EntityType.PLAYER) {
|
||||||
metaData.players.add(packet1.getUUID());
|
metaData.players.add(packet1.getUUID());
|
||||||
saveMetadata();
|
saveMetadata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case ClientboundDisconnectPacket ignored -> {
|
||||||
if (packet instanceof ClientboundDisconnectPacket) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case ClientboundTrackedWaypointPacket ignored -> {
|
||||||
if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) {
|
|
||||||
packet = new ClientboundSetTimePacket(packet1.dayTime(), recorderOption.forceDayTime, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) {
|
|
||||||
ClientboundGameEventPacket.Type type = packet1.getEvent();
|
|
||||||
if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
default -> {
|
||||||
savePacket(packet);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) {
|
||||||
|
packet = new ClientboundSetTimePacket(packet1.dayTime(), recorderOption.forceDayTime, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) {
|
||||||
|
ClientboundGameEventPacket.Type type = packet1.getEvent();
|
||||||
|
if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
savePacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveMetadata() {
|
private void saveMetadata() {
|
||||||
@@ -235,36 +249,23 @@ public class Recorder extends Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> saveRecording(File dest, boolean save) {
|
public CompletableFuture<Void> saveRecording(File dest, boolean save) {
|
||||||
isSaved = true;
|
if (!isSaving.compareAndSet(false, true)) {
|
||||||
if (!isSaving) {
|
|
||||||
isSaving = true;
|
|
||||||
metaData.duration = (int) lastPacket;
|
|
||||||
return CompletableFuture.runAsync(() -> {
|
|
||||||
saveMetadata();
|
|
||||||
boolean interrupted = false;
|
|
||||||
try {
|
|
||||||
if (save) {
|
|
||||||
replayFile.closeAndSave(dest);
|
|
||||||
} else {
|
|
||||||
replayFile.closeNotSave();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new CompletionException(e);
|
|
||||||
} finally {
|
|
||||||
if (interrupted) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, runnable -> {
|
|
||||||
final Thread thread = new Thread(runnable, "Recording file save thread");
|
|
||||||
thread.start();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
LOGGER.warning("saveRecording() called twice");
|
LOGGER.warning("saveRecording() called twice");
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.failedFuture(new IllegalStateException("saveRecording() called twice"));
|
||||||
throw new IllegalStateException("saveRecording() called twice");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
isSaved = true;
|
||||||
|
metaData.duration = (int) lastPacket;
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
replayFile.saveMetaData(metaData);
|
||||||
|
if (save) {
|
||||||
|
replayFile.closeAndSave(dest);
|
||||||
|
} else {
|
||||||
|
replayFile.closeNotSave();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
}, saveService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ import java.nio.file.Files;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import static org.leavesmc.leaves.replay.Recorder.LOGGER;
|
import static org.leavesmc.leaves.replay.Recorder.LOGGER;
|
||||||
import static org.leavesmc.leaves.replay.Recorder.saveService;
|
|
||||||
|
|
||||||
public class ReplayFile {
|
public class ReplayFile {
|
||||||
|
|
||||||
@@ -59,8 +59,10 @@ public class ReplayFile {
|
|||||||
private final File metaFile;
|
private final File metaFile;
|
||||||
|
|
||||||
private final Map<ConnectionProtocol, ProtocolInfo<?>> protocols;
|
private final Map<ConnectionProtocol, ProtocolInfo<?>> protocols;
|
||||||
|
private final ExecutorService saveService;
|
||||||
|
|
||||||
public ReplayFile(@NotNull File name) throws IOException {
|
public ReplayFile(@NotNull File name, ExecutorService saveService) throws IOException {
|
||||||
|
this.saveService = saveService;
|
||||||
this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp");
|
this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp");
|
||||||
if (tmpDir.exists()) {
|
if (tmpDir.exists()) {
|
||||||
if (!ReplayFile.deleteDir(tmpDir)) {
|
if (!ReplayFile.deleteDir(tmpDir)) {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ public class ServerPhotographer extends ServerPlayer {
|
|||||||
GameProfile profile = new GameProfile(UUID.randomUUID(), state.id);
|
GameProfile profile = new GameProfile(UUID.randomUUID(), state.id);
|
||||||
|
|
||||||
ServerPhotographer photographer = new ServerPhotographer(server, world, profile);
|
ServerPhotographer photographer = new ServerPhotographer(server, world, profile);
|
||||||
|
photographer.absSnapTo(state.loc.x(), state.loc.y(), state.loc.z(), state.loc.getYaw(), state.loc.getPitch());
|
||||||
|
|
||||||
photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id));
|
photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id));
|
||||||
photographer.saveFile = new File("replay", state.id + ".mcpr");
|
photographer.saveFile = new File("replay", state.id + ".mcpr");
|
||||||
photographer.createState = state;
|
photographer.createState = state;
|
||||||
@@ -146,6 +148,10 @@ public class ServerPhotographer extends ServerPlayer {
|
|||||||
this.followPlayer = followPlayer;
|
this.followPlayer = followPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerPlayer getFollowPlayer() {
|
||||||
|
return followPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
public void setSaveFile(File saveFile) {
|
public void setSaveFile(File saveFile) {
|
||||||
this.saveFile = saveFile;
|
this.saveFile = saveFile;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user