diff --git a/README.md b/README.md
index 7ff29ab..32ec4ab 100644
--- a/README.md
+++ b/README.md
@@ -155,11 +155,15 @@ After you boot the server a new file will be generated in the path
"player_recording_path": "./recordings/players",
"max_file_size": "0GB",
"restart_after_max_file_size": false,
+ "include_compressed_in_status": true,
+ "fixed_daylight_cycle": -1,
"pause_unloaded_chunks": false,
"pause_notify_players": true,
"fix_carpet_bot_view_distance": false,
"ignore_sound_packets": false,
"ignore_light_packets": true,
+ "ignore_chat_packets": false,
+ "ignore_scoreboard_packets": false,
"optimize_explosion_packets": true,
"optimize_entity_packets": false,
"player_predicate": {
@@ -181,9 +185,12 @@ After you boot the server a new file will be generated in the path
| `"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.
|
| `"restart_after_max_file_size"` | If a max file size is set and this limit is reached then the replay recording will automatically restart creating a new replay file.
|
| `"include_compressed_in_status"` | Includes the compressed file size of the replays when you do `/replay status`, for long replays this may cause the status message to take a while to be displayed, so you can disable it.
|
-| `"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. |
+| `"fixed_daylight_cycle"` |
This fixes the daylight cycle in the replay if you do not want the constant day-night cycle in long timelapses. This should be set to the time of day in ticks, e.g. `6000` (midday). To disable the fixed daylight cycle set the value to `-1`.
|
+| `"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.
|
+| `"ignore_chat_packets"` | Stops chat packets (from both the server and other players) from being recorded if they are not necessary for your replay.
|
+| `"ignore_scoreboard_packets"` | Stops scoreboard packets from being recorded (for example, if you have a scoreboard displaying digs then this will not appear, and player's scores will also not be recorded).
|
| `"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.
|
| `"player_predicate"` | The predicate for recording players automatically, more information in the [Predicates](#predicates-config) section.
|
diff --git a/build.gradle.kts b/build.gradle.kts
index 354e55d..3672105 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
kotlin("jvm")
kotlin("plugin.serialization") version "1.9.21"
id("me.modmuss50.mod-publish-plugin") version "0.4.5"
+ id("com.github.johnrengelman.shadow") version "8.1.1"
id("fabric-loom")
`maven-publish`
java
@@ -10,6 +11,10 @@ plugins {
group = property("maven_group")!!
version = property("mod_version")!!
+val releaseVersion = "${project.version}+mc${project.property("minecraft_version")}"
+
+val shade by configurations.creating
+
repositories {
maven {
url = uri("https://maven.parchmentmc.org/")
@@ -43,8 +48,11 @@ dependencies {
// I've had some issues with ReplayStudio and slf4j (in dev)
// Simplest workaround that I've found is just to unzip the
// jar and yeet the org.slf4j packages then rezip the jar.
- include(modImplementation("com.github.ReplayMod:ReplayStudio:6cd39b0874") {
+ shade(modImplementation("com.github.ReplayMod:ReplayStudio:6cd39b0874") {
exclude(group = "org.slf4j")
+ exclude(group = "it.unimi.dsi")
+ exclude(group = "org.apache.commons")
+ exclude(group = "commons-cli")
exclude(group = "com.google.guava", module = "guava-jdk5")
exclude(group = "com.google.guava", module = "guava")
exclude(group = "com.google.code.gson", module = "gson")
@@ -66,10 +74,6 @@ loom {
}
}
-tasks.remapJar {
- archiveVersion.set("${project.version}+mc${project.property("minecraft_version")}")
-}
-
tasks {
processResources {
inputs.property("version", project.version)
@@ -78,15 +82,37 @@ tasks {
}
}
- jar {
+ remapJar {
+ archiveVersion.set(releaseVersion)
+
+ inputFile.set(shadowJar.get().archiveFile)
+ }
+
+ remapSourcesJar {
+ archiveVersion.set(releaseVersion)
+ }
+
+ shadowJar {
+ destinationDirectory.set(File("./build/devlibs"))
+ isZip64 = true
+
from("LICENSE")
+
+ relocate("com.github.steveice10", "shadow.server_replay.com.github.steveice10")
+ configurations = listOf(shade)
+
+ archiveClassifier = "shaded"
}
publishMods {
file = remapJar.get().archiveFile
changelog.set(
"""
- - Port to 1.20.1
+ - Added some extra meta-data to replay files (to help with debugging)
+ - Added extra configurations:
+ - `fixed_daylight_cycle` - allows you to set a fixed time of day for the recording
+ - `ignore_chat_packets` - ignore all chat packets
+ - `ignore_scoreboard_packets` - ignore all scoreboard packets
""".trimIndent()
)
type = STABLE
diff --git a/gradle.properties b/gradle.properties
index f8f3981..e044a15 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,6 +15,6 @@ permissions_version=0.3-SNAPSHOT
fabric_version=0.87.2+1.19.4
carpet_version=1.4.101
-mod_version=1.0.4
+mod_version=1.0.5
org.gradle.jvmargs=-Xmx4000m
\ No newline at end of file
diff --git a/src/main/kotlin/me/senseiwells/replay/api/RejoinedPacketSender.kt b/src/main/kotlin/me/senseiwells/replay/api/RejoinedPacketSender.kt
new file mode 100644
index 0000000..f9858f3
--- /dev/null
+++ b/src/main/kotlin/me/senseiwells/replay/api/RejoinedPacketSender.kt
@@ -0,0 +1,10 @@
+package me.senseiwells.replay.api
+
+import me.senseiwells.replay.chunk.ChunkRecorder
+import me.senseiwells.replay.player.PlayerRecorder
+
+interface RejoinedPacketSender {
+ fun recordAdditionalPlayerPackets(recorder: PlayerRecorder)
+
+ fun recordAdditionalChunkPackets(recorder: ChunkRecorder)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/me/senseiwells/replay/api/ReplaySenders.kt b/src/main/kotlin/me/senseiwells/replay/api/ReplaySenders.kt
new file mode 100644
index 0000000..78d021d
--- /dev/null
+++ b/src/main/kotlin/me/senseiwells/replay/api/ReplaySenders.kt
@@ -0,0 +1,9 @@
+package me.senseiwells.replay.api
+
+object ReplaySenders {
+ internal val senders = ArrayList()
+
+ fun addSender(sender: RejoinedPacketSender) {
+ this.senders.add(sender)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/me/senseiwells/replay/chunk/ChunkRecorder.kt b/src/main/kotlin/me/senseiwells/replay/chunk/ChunkRecorder.kt
index d8fcad0..37af5d8 100644
--- a/src/main/kotlin/me/senseiwells/replay/chunk/ChunkRecorder.kt
+++ b/src/main/kotlin/me/senseiwells/replay/chunk/ChunkRecorder.kt
@@ -1,5 +1,7 @@
package me.senseiwells.replay.chunk
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
import com.mojang.authlib.GameProfile
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.mixin.chunk.WitherBossAccessor
@@ -20,6 +22,7 @@ import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.boss.wither.WitherBoss
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.levelgen.Heightmap
+import org.apache.commons.lang3.builder.ToStringBuilder
import org.jetbrains.annotations.ApiStatus.Internal
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
@@ -101,6 +104,20 @@ class ChunkRecorder internal constructor(
return super.getTimestamp() - this.totalPausedTime - this.getCurrentPause()
}
+ override fun appendToStatus(builder: ToStringBuilder) {
+ builder.append("chunks_world", this.chunks.level.dimension().location())
+ builder.append("chunks_from", this.chunks.from)
+ builder.append("chunks_to", this.chunks.to)
+ }
+
+ override fun addMetadata(map: MutableMap) {
+ super.addMetadata(map)
+ map["chunks_world"] = JsonPrimitive(this.chunks.level.dimension().location().toString())
+ map["chunks_from"] = JsonPrimitive(this.chunks.from.toString())
+ map["chunks_to"] = JsonPrimitive(this.chunks.to.toString())
+ map["paused_time"] = JsonPrimitive(this.totalPausedTime)
+ }
+
override fun canContinueRecording(): Boolean {
return true
}
diff --git a/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt b/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt
index 7f708b1..96fe5bb 100644
--- a/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt
+++ b/src/main/kotlin/me/senseiwells/replay/commands/ReplayCommand.kt
@@ -14,7 +14,6 @@ import me.senseiwells.replay.chunk.ChunkRecorders
import me.senseiwells.replay.config.ReplayConfig
import me.senseiwells.replay.player.PlayerRecorders
import me.senseiwells.replay.recorder.ReplayRecorder
-import me.senseiwells.replay.util.FileUtils
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.SharedSuggestionProvider
@@ -23,8 +22,6 @@ import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
-import org.apache.commons.lang3.builder.StandardToStringStyle
-import org.apache.commons.lang3.builder.ToStringBuilder
import java.util.concurrent.CompletableFuture
object ReplayCommand {
@@ -290,19 +287,12 @@ object ReplayCommand {
}
private fun status(context: CommandContext): Int {
- val style = StandardToStringStyle().apply {
- fieldSeparator = ", "
- fieldNameValueSeparator = " = "
- isUseClassName = false
- isUseIdentityHashCode = false
- }
-
val builder = StringBuilder("ServerReplay is ")
.append(if (ServerReplay.config.enabled) "enabled" else "disabled")
.append("\n")
- val players = this.getStatusFuture("Players", PlayerRecorders.all(), style)
- val chunks = this.getStatusFuture("Chunks", ChunkRecorders.all(), style)
+ val players = this.getStatusFuture("Players", PlayerRecorders.all())
+ val chunks = this.getStatusFuture("Chunks", ChunkRecorders.all())
CompletableFuture.runAsync {
for (player in players) {
@@ -332,31 +322,13 @@ object ReplayCommand {
private fun getStatusFuture(
type: String,
- recorders: Collection,
- style: StandardToStringStyle,
+ recorders: Collection
): List> {
if (recorders.isNotEmpty()) {
val futures = ArrayList>()
futures.add(CompletableFuture.completedFuture("Currently Recording $type:\n"))
for (recorder in recorders) {
- val seconds = recorder.getTotalRecordingTime() / 1000
- val hours = seconds / 3600
- val minutes = seconds % 3600 / 60
- val secs = seconds % 60
- val time = "%02d:%02d:%02d".format(hours, minutes, secs)
-
- val sub = ToStringBuilder(recorder, style)
- .append("name", recorder.getName())
- .append("time", time)
- .append("raw", FileUtils.formatSize(recorder.getRawRecordingSize()))
- if (ServerReplay.config.includeCompressedReplaySizeInStatus) {
- val compressed = recorder.getCompressedRecordingSize()
- futures.add(compressed.thenApplyAsync {
- "${sub.append("compressed", FileUtils.formatSize(it))}\n"
- })
- } else {
- futures.add(CompletableFuture.completedFuture("$sub\n"))
- }
+ futures.add(recorder.getStatusWithSize())
}
return futures
}
diff --git a/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt b/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt
index e5ac30a..2ec2d65 100644
--- a/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt
+++ b/src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt
@@ -5,9 +5,7 @@ import kotlinx.serialization.EncodeDefault.Mode
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.decodeFromStream
-import kotlinx.serialization.json.encodeToStream
+import kotlinx.serialization.json.*
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.chunk.ChunkRecorders
import me.senseiwells.replay.config.chunk.ChunkAreaConfig
@@ -59,6 +57,9 @@ class ReplayConfig {
@SerialName("include_compressed_in_status")
var includeCompressedReplaySizeInStatus = true
+ @SerialName("fixed_daylight_cycle")
+ var fixedDaylightCycle = -1L
+
@SerialName("pause_unloaded_chunks")
var skipWhenChunksUnloaded = false
@SerialName("pause_notify_players")
@@ -69,6 +70,10 @@ class ReplayConfig {
var ignoreSoundPackets = false
@SerialName("ignore_light_packets")
var ignoreLightPackets = true
+ @SerialName("ignore_chat_packets")
+ var ignoreChatPackets = false
+ @SerialName("ignore_scoreboard_packets")
+ var ignoreScoreboardPackets = false
@SerialName("optimize_explosion_packets")
var optimizeExplosionPackets = true
@SerialName("optimize_entity_packets")
@@ -147,5 +152,9 @@ class ReplayConfig {
ServerReplay.logger.error("Failed to serialize replay config", e)
}
}
+
+ internal fun toJson(config: ReplayConfig): JsonElement {
+ return json.encodeToJsonElement(config)
+ }
}
}
diff --git a/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorder.kt b/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorder.kt
index 212a92b..ef20ace 100644
--- a/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorder.kt
+++ b/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorder.kt
@@ -1,6 +1,8 @@
package me.senseiwells.replay.player
import com.mojang.authlib.GameProfile
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
import me.senseiwells.replay.mixin.rejoin.ChunkMapAccessor
import me.senseiwells.replay.mixin.rejoin.TrackedEntityAccessor
import me.senseiwells.replay.recorder.ChunkSender
@@ -73,6 +75,11 @@ class PlayerRecorder internal constructor(
this.record(ClientboundRemoveEntitiesPacket(player.id))
}
+ override fun addMetadata(map: MutableMap) {
+ super.addMetadata(map)
+ map["player_name"] = JsonPrimitive(this.profile.name)
+ }
+
override fun canContinueRecording(): Boolean {
return this.player != null
}
diff --git a/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorders.kt b/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorders.kt
index 10305f7..de80598 100644
--- a/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorders.kt
+++ b/src/main/kotlin/me/senseiwells/replay/player/PlayerRecorders.kt
@@ -2,12 +2,14 @@ package me.senseiwells.replay.player
import com.mojang.authlib.GameProfile
import me.senseiwells.replay.ServerReplay
+import me.senseiwells.replay.api.RejoinedPacketSender
import me.senseiwells.replay.recorder.ReplayRecorder
import me.senseiwells.replay.rejoin.RejoinedReplayPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import java.util.*
import java.util.concurrent.CompletableFuture
+import kotlin.collections.ArrayList
object PlayerRecorders {
private val players = LinkedHashMap()
diff --git a/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt b/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt
index f4469bd..cb8fda9 100644
--- a/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt
+++ b/src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt
@@ -10,6 +10,11 @@ import com.replaymod.replaystudio.protocol.PacketTypeRegistry
import com.replaymod.replaystudio.replay.ReplayMetaData
import io.netty.buffer.Unpooled
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.encodeToStream
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.config.ReplayConfig
import me.senseiwells.replay.util.DebugPacketData
@@ -26,6 +31,8 @@ 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.builder.StandardToStringStyle
+import org.apache.commons.lang3.builder.ToStringBuilder
import org.jetbrains.annotations.ApiStatus.Internal
import java.io.IOException
import java.net.URL
@@ -181,6 +188,33 @@ abstract class ReplayRecorder(
return CompletableFuture.supplyAsync({ this.replay.getCompressedFileSize() }, this.executor)
}
+ fun getStatusWithSize(): CompletableFuture {
+ val builder = ToStringBuilder(this, StandardToStringStyle().apply {
+ fieldSeparator = ", "
+ fieldNameValueSeparator = " = "
+ isUseClassName = false
+ isUseIdentityHashCode = false
+ })
+ val seconds = this.getTotalRecordingTime() / 1000
+ val hours = seconds / 3600
+ val minutes = seconds % 3600 / 60
+ val secs = seconds % 60
+ val time = "%02d:%02d:%02d".format(hours, minutes, secs)
+ builder.append("name", this.getName())
+ builder.append("time", time)
+
+ this.appendToStatus(builder)
+
+ builder.append("raw_size", FileUtils.formatSize(this.getRawRecordingSize()))
+ if (ServerReplay.config.includeCompressedReplaySizeInStatus) {
+ val compressed = this.getCompressedRecordingSize()
+ return compressed.thenApplyAsync {
+ "${builder.append("compressed_size", FileUtils.formatSize(it))}"
+ }
+ }
+ return CompletableFuture.completedFuture(builder.toString())
+ }
+
@Internal
fun getDebugPacketData(): String {
return this.packets.values
@@ -213,6 +247,15 @@ abstract class ReplayRecorder(
return this.getTotalRecordingTime()
}
+ protected open fun appendToStatus(builder: ToStringBuilder) {
+
+ }
+
+ protected open fun addMetadata(map: MutableMap) {
+ map["name"] = JsonPrimitive(this.getName())
+ map["settings"] = ReplayConfig.toJson(ServerReplay.config)
+ }
+
abstract fun getName(): String
protected abstract fun start(): Boolean
@@ -334,6 +377,13 @@ abstract class ReplayRecorder(
this.executor.execute {
this.replay.writeMetaData(registry, this.meta)
+
+ this.replay.write(ENTRY_SERVER_REPLAY_META).use {
+ val json = HashMap()
+ this.addMetadata(json)
+ @OptIn(ExperimentalSerializationApi::class)
+ Json.encodeToStream(json, it)
+ }
}
}
@@ -414,4 +464,8 @@ abstract class ReplayRecorder(
}
return false
}
+
+ companion object {
+ private const val ENTRY_SERVER_REPLAY_META = "server_replay_meta.json"
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/me/senseiwells/replay/rejoin/RejoinedReplayPlayer.kt b/src/main/kotlin/me/senseiwells/replay/rejoin/RejoinedReplayPlayer.kt
index ebba6bd..9d0e63b 100644
--- a/src/main/kotlin/me/senseiwells/replay/rejoin/RejoinedReplayPlayer.kt
+++ b/src/main/kotlin/me/senseiwells/replay/rejoin/RejoinedReplayPlayer.kt
@@ -1,6 +1,9 @@
package me.senseiwells.replay.rejoin
+import me.senseiwells.replay.api.ReplaySenders
+import me.senseiwells.replay.chunk.ChunkRecorder
import me.senseiwells.replay.mixin.common.PlayerListAccessor
+import me.senseiwells.replay.player.PlayerRecorder
import me.senseiwells.replay.recorder.ReplayRecorder
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.minecraft.nbt.CompoundTag
@@ -113,5 +116,12 @@ class RejoinedReplayPlayer private constructor(
for (mobEffectInstance in this.activeEffects) {
this.recorder.record(ClientboundUpdateMobEffectPacket(this.id, mobEffectInstance))
}
+
+ for (sender in ReplaySenders.senders) {
+ when (this.recorder) {
+ is PlayerRecorder -> sender.recordAdditionalPlayerPackets(this.recorder)
+ is ChunkRecorder -> sender.recordAdditionalChunkPackets(this.recorder)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/me/senseiwells/replay/util/ReplayOptimizerUtils.kt b/src/main/kotlin/me/senseiwells/replay/util/ReplayOptimizerUtils.kt
index e2db351..5bbca94 100644
--- a/src/main/kotlin/me/senseiwells/replay/util/ReplayOptimizerUtils.kt
+++ b/src/main/kotlin/me/senseiwells/replay/util/ReplayOptimizerUtils.kt
@@ -33,8 +33,21 @@ object ReplayOptimizerUtils {
ClientboundPlayerAbilitiesPacket::class.java,
ClientboundLoginCompressionPacket::class.java,
ClientboundCommandSuggestionsPacket::class.java,
+ ClientboundCustomChatCompletionsPacket::class.java,
ClientboundCommandsPacket::class.java
)
+ private val CHAT = setOf>>(
+ ClientboundPlayerChatPacket::class.java,
+ ClientboundDeleteChatPacket::class.java,
+ ClientboundSystemChatPacket::class.java,
+ ClientboundDisguisedChatPacket::class.java
+ )
+ private val SCOREBOARD = setOf>>(
+ ClientboundSetScorePacket::class.java,
+ ClientboundResetScorePacket::class.java,
+ ClientboundSetObjectivePacket::class.java,
+ ClientboundSetDisplayObjectivePacket::class.java
+ )
private val SOUNDS = setOf>>(
ClientboundSoundPacket::class.java,
ClientboundSoundEntityPacket::class.java
@@ -73,10 +86,22 @@ object ReplayOptimizerUtils {
return true
}
+ val time = ServerReplay.config.fixedDaylightCycle
+ if (time >= 0 && packet is ClientboundSetTimePacket && packet.dayTime != -time) {
+ recorder.record(ClientboundSetTimePacket(packet.gameTime, time, false))
+ return true
+ }
+
val type = packet::class.java
if (ServerReplay.config.ignoreSoundPackets && SOUNDS.contains(type)) {
return true
}
+ if (ServerReplay.config.ignoreChatPackets && CHAT.contains(type)) {
+ return true
+ }
+ if (ServerReplay.config.ignoreScoreboardPackets && SCOREBOARD.contains(type)) {
+ return true
+ }
return IGNORED.contains(type)
}