senseiwells / ServerReplay

A completely server-side implementation of the replay mod, this mod allows you to record multiple players that are online on a server at a time, as well as any given chunk area. This will produce replay files which can then be used with the replay mod for rendering.
MIT License
99 stars 7 forks source link
minecraft-mod replay-mod server-replay

Server Replay

English | 中文

A completely server-side implementation of the replay mod, this mod allows you to record multiple players that are online, or chunk areas, on a server at a time. This will produce replay files which can then be used with the replay mod for rendering.

Modrinth download

Why Server-Side?

Compared to the client Replay Mod recording server-side has many benefits:

However, there are also some downsides and known issues:

Usage

This mod requires the fabric launcher, fabric-api, and fabric-kotlin.

There are two ways of recording on the server, you can either configure it to follow and record players from their view. Alternatively, you can record a static area of chunks.

Quick Start

This section of the documentation will briefly guide you through a basic setup. As well as containing some important information.

Players

To record a player on your server you can run /replay start players <player(s)>, for example:

/replay start players senseiwells
/replay start players @a
/replay start players @a[gamemode=survival]

Player recorders are tied to the player and will record at the servers view distance.

If the player leaves or the server stops the replay will automatically stop and save.

Alternatively if you wish to stop the recording manually you can run /replay stop players <player(s)> <save?>, using this command you can also stop a recording without saving it, for example:

/replay stop players senseiwells
/replay stop players @r
/replay stop players senseiwells false

The replay will then be saved to your "player_recording_path" location specified in a folder with the player's uuid. By default, this will be in ./recordings/players/<uuid>/<date-and-time>.mcpr.

This file can then be put in ./replay_recordings on your client and be opened with replay mod.

[!NOTE]
If you are going to record carpet bots you most likely want to enable "fix_carpet_bot_view_distance" in the config otherwise only an area of 2 chunks around the carpet bot will be recorded.

Chunks

[!NOTE] While the mod will record the chunks you specify, the Minecraft client will not render the outermost chunks. So to record an area of visible chunks, you must add one chunk to your border, e.g. recording a visible area from -5, -5 to 5, 5 you must record between -6, -6 and 6, 6.

To record an area of chunks on your server you can run /replay start chunks from <chunkFromX> <chunkFromZ> to <chunkToX> <chunkToZ> in <dimension?> named <name?>, for example:

/replay start chunks from -5 -5 to 5 5 in minecraft:overworld named MyChunkRecording
/replay start chunks from 54 67 to 109 124
/replay start chunks from 30 30 to 60 60 in minecraft:the_nether 

Alternatively you can specify a chunk and a radius around it to be recorded /replay start chunks around <chunkX> <chunkZ> radius <radius> in <dimension?> named <name?>, for example:

/replay start chunks around 0 0 radius 5
/replay start chunks around 67 12 radius 16 in minecraft:overworld named Perimeter Recorder

Chunk recorders are static and cannot move, they record the specified chunks. An important thing to note is that when the replay starts, the specified chunks will be loaded (and generated if necessary). However, after this, the chunk recorder does not load the chunks.

You can further configure this with the "chunk_recorder_load_radius" setting, which will set a maximum initial radius that the chunk recorder will load, any chunks outside this radius that are recorded will need to be loaded 'naturally' to be recorded.

If the server stops, the replay will automatically stop and save.

Alternatively if you wish to stop the recording manually you can run /replay stop chunks from <chunkFromX> <chunkFromZ> to <chunkToX> <chunkToZ> in <dimension?> <save?>, using this command you can also stop a recording without saving it, for example:

/replay stop chunks from 0 0 to 5 5 in minecraft:overworld false
/replay stop chunks from 54 67 to 109 124

You can also stop the chunks by using their name using /replay stop chunks named <name> <save?>, for example:

/replay stop chunks named "Perimeter Recorder" false
/replay stop chunks named MyChunkRecording

The replay will then be saved to your "chunk_recording_path" location specified in a folder with the chunk recorders name. By default, this will be in ./recordings/chunks/<name>/<date-and-time>.mcpr.

This file can then be put in ./replay_recordings on your client and be opened with replay mod.

Viewing

After a replay has finished recording, you are able to view the replay completely server-side. The player viewing the replay will be completely removed from the actual server while they view the replay, and will be treated as if they are offline.

Essentially, this just "runs" another server that sends the client packets. This runs asynchronously from the main server, so there is little to no impact on performance.

When a replay has finished recording, you can click on the green text in chat to view the replay that just finished; this will autocomplete a command in chat for you. The command to view chunk replays is: /replay view chunks <name> <date-time>, and for players: /replay view players <uuid> <date-time>, for example:

/replay view player d4fca8c4-e083-4300-9a73-bf438847861c "2024-05-11--19-19-55"
/replay view chunks "Chunks (183, 166) to (203, 186)" "2024-05-11--19-19-55"

You will then be teleported to the new "server" where the replay will start playing. You will only have access to a limited set of commands when viewing replays, these include:

If you disconnect while watching a replay, you will be brought back to the server when you login.

Downloading

You can download any replay from the server if "allow_downloading_replays" is enabled in the config and the server-ip and downloading port is set up correctly.

You can use the /replay download command to retrieve the URL to download the specified replay, for example:

/replay download players d4fca8c4-e083-4300-9a73-bf438847861c "2024-05-11--19-19-55"
/replay download chunks "Chunks (183, 166) to (203, 186)" "2024-05-11--19-19-55"

This will send you a chat message; you can click on the link provided which will download the file.

Commands

A note for all commands; players must either have op (level 4), alternatively if you have a permission mod (for example, LuckPerms) players can have the permission replay.commands.replay to access these commands.

Configuring

After you boot the server a new file will be generated in the path ./config/ServerReplay/config.json, by default, it should look like:

{
  "enabled": false,
  "world_name": "World",
  "server_name": "Server",
  "chunk_recording_path": "./recordings/chunks",
  "player_recording_path": "./recordings/players",
  "player_recording_name": "{uuid}",
  "max_file_size": "0GB",
  "restart_after_max_file_size": false,
  "max_duration": "0s",
  "restart_after_max_duration": false,
  "recover_unsaved_replays": true,
  "include_compressed_in_status": true,
  "fixed_daylight_cycle": -1,
  "chunk_recorder_load_radius": -1,
  "pause_unloaded_chunks": false,
  "pause_notify_players": true,
  "notify_admins_of_status": true,
  "fix_carpet_bot_view_distance": false,
  "include_resource_packs": true,
  "ignore_sound_packets": false,
  "ignore_light_packets": true,
  "ignore_chat_packets": false,
  "ignore_action_bar_packets": false,
  "ignore_scoreboard_packets": false,
  "optimize_explosion_packets": true,
  "optimize_entity_packets": false,
  "record_voice_chat": false,
  "replay_server_ip": null,
  "allow_downloading_replays": false,
  "player_predicate": {
    "type": "none"
  },
  "chunks": []
}
Config Description
"enabled"

By default replay functionality is disabled. You can enable it by by editing the config.json and running /replay reload or running the /replay [enable\|disable] command.

"world_name"

The name of the world that will appear on the replay file.

"server_name"

The name of the server that will appear on the replay file.

"player_recording_path"

The path where you want player recordings to be saved.

"chunk_recording_path"

The path where you want chunk recordings to be saved.

"player_recording_name"

This determines the name of each specific player's recording directory. By default is set to "{uuid}" which uses the player's uuid. You can also insert the player's name with "{username}". You could for example have: "Recordings for: {username} ({uuid})".

"max_file_size"

The maximum replay file size you want to allow to record, this is any number followed by a unit, e.g. 5.2mb.

If this limit is reached then the replay recorder will stop. This is only approximate, expect the real file size to be slightly larger. Set this to 0 to not have a limit.

Be warned that this may impact server performance if your max file size is large, in order to check whether a file is too big (>5GB) it must be compressed which can be very expensive. You may check the time until the next file size check by running /replay status.

"restart_after_max_file_size"

If the max_file_size is set and this limit is reached then the replay recording will automatically restart creating a new replay file.

"max_duration"

Sets the maximum duration for a replay, once the replay has recorded for the specified amount of time it will stop, this is any number followed by units (you may also have multiple units), e.g. 4h 35m 2.1s. Set this to 0 to not have a max duration limit. Note: if a recorder is paused it's duration does not increase.

"restart_after_max_duration"

If the max_duration is set and this limit is reached then the replay recording will automatically restart creating a new replay file.

"recover_unsaved_replays"

This tries to recover any unsaved replays, for example if your server crashes or stops before a replay is stopped or has finished saving, this does not guarantee that the replay will not be corrupt, but it will try to salvage what is available.

"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.

"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.

"chunk_recorder_load_radius"

This sets the default chunk recorder loading radius, this is useful when you want to record a very large area and you don't want all of the recorded chunks to be loaded at once.

For example if you are recording a 13x13 chunk area, you could set the radius to 3, so the center-most 7x7 would be initially loaded, the rest of the chunks will then be recorded whenever they're 'naturally' loaded.

Set this to -1 to load all chunks.

"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.

"notify_admins_of_status"

When enabled this will notify admins of when a replay starts, when a replay ends, and when a replay has finished saving, as well as any errors that occur.

"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.

"include_resource_packs"

If enabled all server-side resource packs will be copied in the replay file to ensure correct playback. Disabling this will decrease file size but instead it'll try to download the pack from the original source whenever viewing the replay, there is no guarantee that this will work correctly.

"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_action_bar_packets"

Stops action bar packets 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.

"replay_server_ip"

This is required if your server uses custom server-side resource packs and you want to be able to view these packs in the server-side replay viewer. This is also required if you want to allow users to download replays.

This should contain the public ip address of your server.

"allow_downloading_replays"

Determines whether users will be able to download recorded replays.

"record_voice_chat"

This enables support for recording voice-chat if you have the simple-voice-chat mod installed, when watching back the replay you must have replay-voice-chat installed.

"player_predicate"

The predicate for recording players automatically, more information in the Predicates section.

"chunks"

The list of chunks to automatically record when the server starts, more information in the Chunks section.

Chunks Config

You can define chunk areas to be recorded automatically when the server starts or when you enable ServerReplay.

Each chunk definition must include: "name", "dimension", "from_x", "to_x", "from_z", and "to_z". For example:

{
  // ...
  "chunks": [
    {
      "name": "My Chunks",
      "dimension": "minecraft:overworld",
      "from_x": -5,
      "from_z": -5,
      "to_x": 5,
      "to_z": 5
    },
    {
      "name": "My Nether Chunks",
      "dimension": "minecraft:the_nether",
      "from_x": 100,
      "from_z": 50,
      "to_x": 90,
      "to_z": 60
    }
    // ...
  ]
}

Predicates Config

You can define a predicate, which determines which players on your server will be recorded automatically. You can do this by specifying whether players have a specific uuid, name, are on a specific team, or whether they are an operator.

After defining a predicate you must run /replay reload in game then players must re-log if they want to be recorded (and meet the predicate criteria).

Most basic option is just to record all players in which case you can use:

{
  // ...
  "player_predicate": {
    "type": "all"
  }
}

If you wanted to only record players with specific names or uuids you can do the following:

{
  // ...
  "player_predicate": {
    "type": "has_name",
    "names": [
      "senseiwells",
      "foobar"
    ]
  }
}
{
  // ...
  "player_predicate": {
    "type": "has_uuid",
    "uuids": [
      "41048400-886d-497d-9d97-9fe7c9b63afa",
      "71266dbd-db0a-484a-b859-3f135590d7a9",
      "47d072ca-d7a2-467c-9b60-de501907e91d",
      "0e324e7f-e78e-4777-b501-7ae08a65b1eb",
      "7d9e24c2-9d0f-479f-81c7-27389624ebb2"
    ]
  }
}

If you only wanted to record operators:

{
  // ...
  "player_predicate": {
    "type": "has_op",
    "level": 4
  }
}

If you only want to record players on specific teams, this is useful for allowing players to be added and removed in-game, as you can just add players to a team and then have them re-log:

{
  // ...
  "player_predicate": {
    "type": "in_team",
    "teams": [
      "Red",
      "Blue",
      "Spectators"
    ]
  }
}

You are also able to negate predicates, using 'not' and combine them using 'or' and 'and'. For example, if you wanted to record all non-operators that also don't have the name 'senseiwells' or is on the red team:

{
  // ...
  "player_predicate": {
    "type": "and",
    "predicates": [
      {
        "type": "not",
        "predicate": {
          "type": "has_op",
          "level": 4
        }
      },
      {
        "type": "not",
        "predicate": {
          "type": "or",
          "predicates": [
            {
              "type": "has_name",
              "names": [
                "senseiwells"
              ]
            },
            {
              "type": "in_team",
              "teams": [
                "Red"
              ]
            }
          ]
        }
      } 
    ]
  }
}

If you are using carpet mod and have the ability to spawn fake players you may want to exclude them from being recorded. You can do this with the is_fake predicate:

{
  // ...
  "player_predicate": {
    "type": "not",
    "predicate": {
      "type": "is_fake"
    }
  }
}

Developers

If you want more control over, when players are recorded, you can implement this into your own mod.

To implement the API into your project, you can add the following to your build.gradle.kts

repositories {
    maven("https://maven.supersanta.me/snapshots")
}

dependencies {
    modImplementation("me.senseiwells:server-replay:1.2.4+1.21.3")
}

Here's a basic example of what you can do:

class ExampleMod: ModInitializer {
    override fun onInitialize() {
        ServerPlayConnectionEvents.JOIN.register { connection, _, _ ->
            val player = connection.player
            if (!PlayerRecorders.has(player)) {
                if (player.level().dimension() == Level.END) {
                    val recorder = PlayerRecorders.create(player)
                    recorder.start(log = true)
                }
            } else {
                val existing = PlayerRecorders.get(player)!!
                existing.getCompressedRecordingSize().thenAccept { size ->
                    println("Replay is $size bytes")
                }
                existing.stop(save = false)
            }
        }

        ServerLifecycleEvents.SERVER_STARTED.register { server ->
            val recorder = ChunkRecorders.create(
                server.overworld(),
                ChunkPos.ZERO,
                ChunkPos(5, 5),
                "Named"
            )
            recorder.start(log = false)
        }
    }
}

If you want to add support to your mod for ServerReplay you can create a plugin:

class MyServerReplayPlugin: ServerReplayPlugin {
    override fun onPlayerReplayStart(recorder: PlayerRecorder) {
        // Send any additional packets for players here
    }

    override fun onChunkReplayStart(recorder: ChunkRecorder) {
        // Send any additional packets for chunks here
    }
}

Then you simply register this in your fabric.mod.json:

{
  // ...
  "entrypoints": {
    "main": [
      // ...
    ],
    "server_replay": [
      "com.example.MyServerReplayPlugin"
    ]
  }
  // ...
}