diff --git a/README.md b/README.md index 47f78b8..ace7816 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ After you boot the server a new file will be generated in the path "fix_carpet_bot_view_distance": false, "ignore_sound_packets": false, "ignore_light_packets": true, + "optimize_tnt_packets": false, "pause_unloaded_chunks": false, "pause_notify_players": true, "player_recording_path": "./recordings/players", @@ -177,6 +178,8 @@ After you boot the server a new file will be generated in the path | `"fix_carpet_bot_view_distance"` |
If you are recording carpet bots you want to enable this as it sets the view distance to the server view distance. Otherwise it will only record a distance of 2 chunks around the bot. | | `"ignore_sound_packets"` |
If you are recording a large area for a timelapse it's unlikely you'll want to record any sounds, these can eat up significant storage space.
| | `"ignore_light_packets"` |Light is calculated on the client as well as on the server so light packets are mostly redundant.
| +| `"optimize_explosion_packets"` |This reduces the file size greatly by not sending the client explosion packets instead just sending the explosion particles and sounds.
| +| `"optimize_entity_packets"` |This reduces the file size by letting the client handle the logic for some entities, e.g. projectiles and tnt. This may cause some inconsistencies however it will likely be negligible.
| | `"pause_unloaded_chunks"` |If an area of chunks is being recorded and the area is unloaded and this is set to `true` then the replay will pause the recording until the chunks are loaded again.
If set to false the chunks will be recorded as if they were loaded.
| | `"pause_notify_players"` |If `pause_unloaded_chunks` is enabled and this is enabled then when the recording for the chunk area is paused or resumed all online players will be notified.
| | `"player_recording_path"` |The path where you want player recordings to be saved.
| diff --git a/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt b/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt index 69592d7..4cfbe70 100644 --- a/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt +++ b/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt @@ -7,7 +7,6 @@ import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.suggestion.SuggestionProvider import me.lucko.fabric.api.permissions.v0.Permissions -import me.senseiwells.replay.ServerReplay import me.senseiwells.replay.chunk.ChunkArea import me.senseiwells.replay.chunk.ChunkRecorder import me.senseiwells.replay.chunk.ChunkRecorders @@ -327,13 +326,15 @@ object ReplayCommand { val secs = seconds % 60 val time = "%02d:%02d:%02d".format(hours, minutes, secs) - val built = ToStringBuilder(recorder, style) + val sub = ToStringBuilder(recorder, style) .append("name", recorder.getName()) .append("time", time) .append("raw", FileUtils.formatSize(recorder.getRawRecordingSize())) .append("compressed", FileUtils.formatSize(compressed.join())) - .toString() - builder.append(built).append("\n") + if (ReplayConfig.debug) { + sub.append("debug", recorder.getDebugPacketData()) + } + builder.append(sub.toString()).append("\n") } } else { builder.append("Not Currently Recording $type").append("\n") diff --git a/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt b/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt index 7c8225e..e11063f 100644 --- a/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt +++ b/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt @@ -32,6 +32,8 @@ object ReplayConfig { @JvmStatic var enabled: Boolean = false + @JvmStatic + var debug: Boolean = false @JvmStatic var skipWhenChunksUnloaded = false @@ -41,6 +43,8 @@ object ReplayConfig { var fixCarpetBotViewDistance = false var ignoreSoundPackets = false var ignoreLightPackets = true + var optimizeExplosionPackets = true + var optimizeEntityPackets = false var worldName = "World" var serverName = "Server" @@ -75,6 +79,9 @@ object ReplayConfig { if (json.has("enabled")) { this.enabled = json.get("enabled").asBoolean } + if (json.has("debug")) { + this.debug = json.get("debug").asBoolean + } if (json.has("world_name")) { this.worldName = json.get("world_name").asString } @@ -97,15 +104,18 @@ object ReplayConfig { if (json.has("ignore_light_packets")) { this.ignoreLightPackets = json.get("ignore_light_packets").asBoolean } + if (json.has("optimize_explosion_packets")) { + this.optimizeExplosionPackets = json.get("optimize_explosion_packets").asBoolean + } + if (json.has("optimize_entity_packets")) { + this.optimizeEntityPackets = json.get("optimize_entity_packets").asBoolean + } if (json.has("pause_unloaded_chunks")) { this.skipWhenChunksUnloaded = json.get("pause_unloaded_chunks").asBoolean } if (json.has("pause_notify_players")) { this.notifyPlayersLoadingChunks = json.get("pause_notify_players").asBoolean } - if (json.has("recording_path")) { - this.playerRecordingPath = Path.of(json.get("recording_path").asString) - } if (json.has("player_recording_path")) { this.playerRecordingPath = Path.of(json.get("player_recording_path").asString) } @@ -131,6 +141,9 @@ object ReplayConfig { try { val json = JsonObject() json.addProperty("enabled", this.enabled) + if (this.debug) { + json.addProperty("debug", true) + } json.addProperty("world_name", this.worldName) json.addProperty("server_name", this.serverName) json.addProperty("max_file_size", this.maxFileSizeString) @@ -138,6 +151,8 @@ object ReplayConfig { json.addProperty("fix_carpet_bot_view_distance", this.fixCarpetBotViewDistance) json.addProperty("ignore_sound_packets", this.ignoreSoundPackets) json.addProperty("ignore_light_packets", this.ignoreLightPackets) + json.addProperty("optimize_explosion_packets", this.optimizeExplosionPackets) + json.addProperty("optimize_entity_packets", this.optimizeEntityPackets) json.addProperty("pause_unloaded_chunks", this.skipWhenChunksUnloaded) json.addProperty("pause_notify_players", this.notifyPlayersLoadingChunks) json.addProperty("player_recording_path", this.playerRecordingPath.pathString) diff --git a/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt b/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt index 8c188a8..0376188 100644 --- a/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt +++ b/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt @@ -12,6 +12,7 @@ import io.netty.buffer.Unpooled import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import me.senseiwells.replay.ServerReplay import me.senseiwells.replay.config.ReplayConfig +import me.senseiwells.replay.util.DebugPacketData import me.senseiwells.replay.util.FileUtils import me.senseiwells.replay.util.ReplayOptimizerUtils import me.senseiwells.replay.util.SizedZipReplayFile @@ -27,10 +28,9 @@ import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.network.protocol.game.ClientboundRespawnPacket import net.minecraft.network.protocol.login.ClientboundGameProfilePacket import net.minecraft.server.MinecraftServer +import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.EntityType -import org.apache.commons.lang3.mutable.MutableInt import org.jetbrains.annotations.ApiStatus.Internal -import org.jetbrains.annotations.VisibleForTesting import java.io.IOException import java.net.URL import java.nio.charset.StandardCharsets @@ -41,7 +41,6 @@ import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import kotlin.collections.HashMap import kotlin.io.path.* import com.github.steveice10.netty.buffer.Unpooled as ReplayUnpooled import net.minecraft.network.protocol.Packet as MinecraftPacket @@ -51,6 +50,7 @@ abstract class ReplayRecorder( protected val profile: GameProfile, private val recordings: Path ) { + private val packets by lazy { Object2ObjectOpenHashMap