9
0
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:
Lumine1909
2025-07-29 06:03:18 -07:00
committed by GitHub
parent 1242a2a66c
commit cadc126902
5 changed files with 83 additions and 56 deletions

View File

@@ -36,6 +36,7 @@ import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent;
import org.slf4j.Logger;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -204,7 +205,7 @@ public class BotList {
playerIO.save(bot);
} else {
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()) {
@@ -305,6 +306,17 @@ public class BotList {
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() {
this.bots.forEach(ServerBot::networkTick);
}

View File

@@ -275,6 +275,12 @@ public class ServerBot extends ServerPlayer {
return this;
}
@Override
public void setServerLevel(ServerLevel level) {
BotList.INSTANCE.updateBotLevel(this, level);
super.setServerLevel(level);
}
@Override
public void knockback(double strength, double x, double z, @Nullable Entity attacker, EntityKnockbackEvent.@NotNull Cause eventCause) {
if (!this.hurtMarked) {

View File

@@ -26,14 +26,17 @@ import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeat
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
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.ClientboundSystemChatPacket;
import net.minecraft.network.protocol.game.ClientboundTrackedWaypointPacket;
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.packs.repository.KnownPack;
import net.minecraft.tags.TagNetworkSerialization;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.PositionMoveRotation;
import net.minecraft.world.flag.FeatureFlags;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,23 +44,27 @@ import org.leavesmc.leaves.LeavesLogger;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
public class Recorder extends Connection {
public static final Executor saveService = Executors.newVirtualThreadPerTaskExecutor();
public static final LeavesLogger LOGGER = LeavesLogger.LOGGER;
public final ExecutorService saveService = Executors.newSingleThreadExecutor();
private final ReplayFile replayFile;
private final ServerPhotographer photographer;
private final RecorderOption recorderOption;
private final RecordMetaData metaData;
private final AtomicBoolean isSaving = new AtomicBoolean(false);
private boolean stopped = false;
private boolean paused = false;
@@ -68,7 +75,6 @@ public class Recorder extends Connection {
private long timeShift = 0;
private boolean isSaved;
private boolean isSaving;
private ConnectionProtocol state = ConnectionProtocol.LOGIN;
public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException {
@@ -77,7 +83,7 @@ public class Recorder extends Connection {
this.photographer = photographer;
this.recorderOption = recorderOption;
this.metaData = new RecordMetaData();
this.replayFile = new ReplayFile(replayFile);
this.replayFile = new ReplayFile(replayFile, saveService);
this.channel = new LocalChannel();
}
@@ -93,6 +99,8 @@ public class Recorder extends Connection {
this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN);
this.startConfiguration();
savePacket(ClientboundPlayerPositionPacket.of(photographer.getId(), PositionMoveRotation.of(photographer), Collections.emptySet()));
if (recorderOption.forceWeather != null) {
setWeather(recorderOption.forceWeather);
}
@@ -170,40 +178,46 @@ public class Recorder extends Connection {
@Override
public void send(@NotNull Packet<?> packet, @Nullable ChannelFutureListener callbacks, boolean flush) {
if (!stopped) {
if (packet instanceof BundlePacket<?> packet1) {
if (stopped) {
return;
}
switch (packet) {
case BundlePacket<?> packet1 -> {
packet1.subPackets().forEach(subPacket -> send(subPacket, null));
return;
}
if (packet instanceof ClientboundAddEntityPacket packet1) {
case ClientboundAddEntityPacket packet1 -> {
if (packet1.getType() == EntityType.PLAYER) {
metaData.players.add(packet1.getUUID());
saveMetadata();
}
}
if (packet instanceof ClientboundDisconnectPacket) {
case ClientboundDisconnectPacket ignored -> {
return;
}
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)) {
case ClientboundTrackedWaypointPacket ignored -> {
return;
}
savePacket(packet);
default -> {
}
}
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() {
@@ -235,36 +249,23 @@ public class Recorder extends Connection {
}
public CompletableFuture<Void> saveRecording(File dest, boolean save) {
isSaved = 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 {
if (!isSaving.compareAndSet(false, true)) {
LOGGER.warning("saveRecording() called twice");
return CompletableFuture.supplyAsync(() -> {
throw new IllegalStateException("saveRecording() called twice");
});
return CompletableFuture.failedFuture(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);
}
}

View File

@@ -34,12 +34,12 @@ import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.leavesmc.leaves.replay.Recorder.LOGGER;
import static org.leavesmc.leaves.replay.Recorder.saveService;
public class ReplayFile {
@@ -59,8 +59,10 @@ public class ReplayFile {
private final File metaFile;
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");
if (tmpDir.exists()) {
if (!ReplayFile.deleteDir(tmpDir)) {

View File

@@ -55,6 +55,8 @@ public class ServerPhotographer extends ServerPlayer {
GameProfile profile = new GameProfile(UUID.randomUUID(), state.id);
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.saveFile = new File("replay", state.id + ".mcpr");
photographer.createState = state;
@@ -146,6 +148,10 @@ public class ServerPhotographer extends ServerPlayer {
this.followPlayer = followPlayer;
}
public ServerPlayer getFollowPlayer() {
return followPlayer;
}
public void setSaveFile(File saveFile) {
this.saveFile = saveFile;
}