Skip to content

Commit

Permalink
updated command and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Hadron67 committed May 15, 2020
1 parent 4b8ffd7 commit f4c1beb
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 84 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

Server-side recording mod for [ReplayMod](https://github.com/ReplayMod/ReplayMod). It records the game using fake players so can be used on servers for 24/7 recording. It's also an alternative way to record single player world.

* Display recorded time, recorded file size, etc. in tab list.
* Can be set to pause automatically when no players are near by.
* Allow players to download recording files via an embedded http server.

Tested in Minecraft 1.14.4, should also work in later versions with only trivial modifications on the code (minecraft version and yarn mapping version of fabric-loom).

## Use
Expand All @@ -14,11 +18,14 @@ Tested in Minecraft 1.14.4, should also work in later versions with only trivial
* `/sreplay player <player name> set sizeLimit <size limit>` Set recording file size limit for the specified bot, in MB. Set to `-1` for unlimited size.
* `/sreplay player <player name> set timeLimit <time limit>` Set recording time limit for the specified bot, in seconds. Set to `-1` for unlimited time length.
* `/sreplay player <player name> set autoRestart <auto restart>` Set auto restart flag. When size limit or time limit exceeds a new recording session will be started if this flag is on.
* `/sreplay player <player name> set autoPause <auto pause>` Set auto pause flag. If this flag is on, recording will be paused while no players near by.
* `/sreplay player <player name> pause|resume` Pause/resume recording of the designated bot.
* `/sreplay player <player name> marker <marker name>` Add a marker on the time line.
* `/sreplay list` List all saved replay file;
* `/sreplay delete <recording file name>` Delete a recording file. Needs confirming using `/sreplay confirm <code>`;
* `/sreplay reload` Reload configuration.
* `/sreplay get <recording file>` Generate a temporary downloading URL for the given recording file. This URL will be invalidated after first request, or a configurable time out is expired.
* `/sreplay server start|stop` Start/stop the http server for downloading recording file.

## Configuration
Cnofiguration file is `config/sreplay.json`, it will be created each time start the mod if not exist.
Expand Down
7 changes: 7 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# SReplay
ReplayMod服务端录制mod,通过使用类似于carpet的假人来录制。单机同样可用。

* 在玩家列表栏里面显示假人已录制的时长、文件大小等;
* 可以设置成当附近没有玩家时暂停录像;
* 允许玩家通过一个内置的http服务器下载录像文件。

目前支持1.14.4,对于新版本应该只需要修改gradle文件中的Minecraft版本和yarn mapping版本。

## 使用
Expand All @@ -11,11 +15,14 @@ ReplayMod服务端录制mod,通过使用类似于carpet的假人来录制。
* `/sreplay player <玩家名> set sizeLimit <文件上限大小>` 设置指定假人的录像文件大小上限,单位是MB。`-1`为无上限;
* `/sreplay player <玩家名> set timeLimit <时间上限>` 设置指定假人的录像时间上限,单位是秒。`-1`为无上限;
* `/sreplay player <玩家名> set autoRestart <auto restart>` 设置自动续录标志。如果该标志为`true`那么当文件或时间上限超过之后就会自动停止录制并重新开始新的录制;
* `/sreplay player <玩家名> set autoPause <auto pause>` 设置自动暂停标志。如果为`true`那么当周围没有玩家的时候就会自动暂停,并在有玩家之后继续录制;
* `/sreplay player <玩家名> pause|resume` 暂停/继续录制;
* `/sreplay player <玩家名> marker <标记名>` 添加一个标记;
* `/sreplay list` 列表所有已保存的录像文件;
* `/sreplay delete <录像文件名>` 删除指定的录像文件,需要用`/sreplay confirm <确认码>`确认;
* `/sreplay reload` 重新加载配置文件。
* `/sreplay get <录像文件名>` 生成一个用于下载给定录像文件的临时URL。当第一个请求或者超时之后这个链接就会自动失效。
* `/sreplay server start|stop` 启动/停止用于下载录像文件的http服务器。

## Configuration
配置文件在`config/sreplay.json`,每次启动时如果文件不存在就会被创建。
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.hadroncfy.sreplay.command;

import com.hadroncfy.sreplay.SReplayMod;
import com.hadroncfy.sreplay.config.TextRenderer;
import com.hadroncfy.sreplay.recording.Photographer;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.server.command.ServerCommandSource;

import static net.minecraft.server.command.CommandManager.literal;
import static net.minecraft.server.command.CommandManager.argument;

public abstract class RecordParameterExecutor<T> implements Command<ServerCommandSource> {
private final Class<T> c;
private final ArgumentType<T> type;
private final String argName;

protected abstract int run(CommandContext<ServerCommandSource> context, Photographer p, T val);

public RecordParameterExecutor(String name, ArgumentType<T> type, Class<T> clazz){
argName = name;
this.type = type;
c = clazz;
}

@Override
public int run(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
final Photographer p = SReplayCommand.requirePlayer(context);
final T val = context.getArgument(argName, c);
final ServerCommandSource src = context.getSource();
if (p != null && run(context, p, val) == 0){
src.getMinecraftServer().getPlayerManager()
.broadcastChatMessage(TextRenderer.render(SReplayMod.getFormats().setParam,
src.getName(),
p.getGameProfile().getName(),
argName,
val.toString()
), true);
}
return 0;
}

public ArgumentBuilder<ServerCommandSource, ?> build(){
return literal(argName)
.then(argument(argName, type).executes(this));
}
}
97 changes: 48 additions & 49 deletions src/main/java/com/hadroncfy/sreplay/command/SReplayCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private int removeMarker(CommandContext<ServerCommandSource> ctx){
return 1;
}
p.getRecorder().removeMarker(id);
src.sendFeedback(render(SReplayMod.getFormats().markerRemoved, name, Integer.toString(id + 1)), true);
src.getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().markerRemoved, ctx.getSource().getName(), name, Integer.toString(id + 1)), false);
}
return 0;
}
Expand Down Expand Up @@ -193,64 +193,63 @@ private int setName(CommandContext<ServerCommandSource> ctx){
return 0;
}
p.setSaveName(name);
ctx.getSource().sendFeedback(render(SReplayMod.getFormats().renamedFile, p.getGameProfile().getName(), name), true);
ctx.getSource().sendFeedback(render(SReplayMod.getFormats().renamedFile, ctx.getSource().getName(), p.getGameProfile().getName(), name), true);
return 1;
}
return 0;
}

private static LiteralArgumentBuilder<ServerCommandSource> buildPlayerParameterCommand(){
return literal("set")
.then(literal("sizeLimit")
.then(argument("sizeLimit", IntegerArgumentType.integer(-1))
.executes(ctx -> {
final Photographer p = requirePlayer(ctx);
if (p != null){
int i = IntegerArgumentType.getInteger(ctx, "sizeLimit");
if (i == -1){
p.getRecordingParam().sizeLimit = -1;
}
else if (i < 10){
ctx.getSource().sendFeedback(SReplayMod.getFormats().sizeLimitTooSmall, true);
}
else {
p.getRecordingParam().sizeLimit = ((long)i) << 20;
}
return 1;
.then(new RecordParameterExecutor<Integer>("sizeLimit", IntegerArgumentType.integer(-1), int.class) {
@Override
protected int run(CommandContext<ServerCommandSource> ctx, Photographer p, Integer val) {
if (val == -1){
p.getRecordingParam().sizeLimit = -1;
}
return 0;
})))
.then(literal("autoRestart")
.then(argument("autoRestart", BoolArgumentType.bool())
.executes(ctx -> {
final Photographer p = requirePlayer(ctx);
if (p != null){
p.getRecordingParam().autoReconnect = BoolArgumentType.getBool(ctx, "autoRestart");
else if (val < 10){
ctx.getSource().sendFeedback(SReplayMod.getFormats().sizeLimitTooSmall, true);
return 1;
}
return 0;
})))
.then(literal("timeLimit")
.then(argument("timeLimit", IntegerArgumentType.integer(-1))
.executes(ctx -> {
final Photographer p = requirePlayer(ctx);
if (p != null){
int i = IntegerArgumentType.getInteger(ctx, "timeLimit");
if (i == -1){
p.getRecordingParam().timeLimit = -1;
}
else if (i < 10){
ctx.getSource().sendFeedback(SReplayMod.getFormats().timeLimitTooSmall, true);
}
else {
p.getRecordingParam().timeLimit = i * 1000;
}
else {
p.getRecordingParam().sizeLimit = ((long)val) << 20;
}
return 0;
}
}.build())
.then(new RecordParameterExecutor<Integer>("timeLimit", IntegerArgumentType.integer(-1), int.class) {
@Override
protected int run(CommandContext<ServerCommandSource> ctx, Photographer p, Integer val) {
if (val == -1){
p.getRecordingParam().timeLimit = -1;
}
else if (val < 10){
ctx.getSource().sendFeedback(SReplayMod.getFormats().timeLimitTooSmall, true);
}
else {
p.getRecordingParam().timeLimit = val * 1000;
}
return 0;
})));
return 0;
}

}.build())
.then(new RecordParameterExecutor<Boolean>("autoRestart", BoolArgumentType.bool(), boolean.class) {
@Override
protected int run(CommandContext<ServerCommandSource> ctx, Photographer p, Boolean val) {
p.getRecordingParam().autoReconnect = val;
return 0;
}
}.build())
.then(new RecordParameterExecutor<Boolean>("autoPause", BoolArgumentType.bool(), boolean.class) {
@Override
protected int run(CommandContext<ServerCommandSource> ctx, Photographer p, Boolean val) {
p.setAutoPause(val);
return 0;
}
}.build());
}

private static Photographer requirePlayer(CommandContext<ServerCommandSource> ctx){
static Photographer requirePlayer(CommandContext<ServerCommandSource> ctx){
String name = StringArgumentType.getString(ctx, "player");
Photographer p = SReplayMod.getFake(ctx.getSource().getMinecraftServer(), name);
if (p != null){
Expand All @@ -272,7 +271,7 @@ public int marker(CommandContext<ServerCommandSource> ctx){
if (p != null){
String name = StringArgumentType.getString(ctx, "marker");
p.getRecorder().addMarker(name);
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().markerAdded, p.getGameProfile().getName(), name), false);
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().markerAdded, ctx.getSource().getName(), p.getGameProfile().getName(), name), false);
return 1;
}
else {
Expand All @@ -284,7 +283,7 @@ public int pause(CommandContext<ServerCommandSource> ctx){
Photographer p = requirePlayer(ctx);
if (p != null){
p.getRecorder().pauseRecording();
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingPaused, p.getGameProfile().getName()), false);
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingPaused, ctx.getSource().getName(), p.getGameProfile().getName()), false);
return 1;
}
else {
Expand All @@ -296,7 +295,7 @@ public int resume(CommandContext<ServerCommandSource> ctx){
Photographer p = requirePlayer(ctx);
if (p != null){
p.getRecorder().resumeRecording();
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingResumed, p.getGameProfile().getName()), false);
ctx.getSource().getMinecraftServer().getPlayerManager().broadcastChatMessage(render(SReplayMod.getFormats().recordingResumed, ctx.getSource().getName(), p.getGameProfile().getName()), false);
return 1;
}
else {
Expand Down
50 changes: 32 additions & 18 deletions src/main/java/com/hadroncfy/sreplay/config/Formats.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private static Text red(String s){
confirmingHint = new LiteralText("[SReplay] 使用")
.append(new LiteralText("/sreplayer confirm $1").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("以确认此次操作")),
deletedRecordingFile = new LiteralText("$1已删除录像文件$2"),
deletedRecordingFile = new LiteralText("[SReplay] $1: 已删除录像文件$2"),
operationCancelled = new LiteralText("[SReplay] 已取消操作"),
incorrectConfirmationCode = red("[SReplay] 确认码不匹配"),
fileNotFound = red("[SReplay] 文件$1不存在"),
Expand All @@ -33,11 +33,17 @@ private static Text red(String s){
playerIsLoggedIn = red("[SReplay] 玩家$1已登录"),
failedToStartRecording = red("[SReplay] 录制失败:$1"),
recordingFileListHead = new LiteralText("[SReplay] 录制文件列表:"),
recordingFileItem = new LiteralText("- $1($2M)").setStyle(new Style().setColor(Formatting.GREEN)),
savingRecordingFile = new LiteralText("正在保存")
recordingFileItem = new LiteralText("- $1($2M) ").setStyle(new Style().setColor(Formatting.GREEN))
.append(new LiteralText("[下载]").setStyle(new Style().setColor(Formatting.BLUE).setClickEvent(
new ClickEvent(Action.RUN_COMMAND, "/sreplay get $1")
)))
.append(new LiteralText("[删除]").setStyle(new Style().setColor(Formatting.RED).setClickEvent(
new ClickEvent(Action.RUN_COMMAND, "/sreplay delete $1")
))),
savingRecordingFile = new LiteralText("[SReplay] 正在保存")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN)))
.append(new LiteralText("的录像文件")),
savedRecordingFile = new LiteralText("已保存")
savedRecordingFile = new LiteralText("[SReplay] 已保存")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN)))
.append(new LiteralText("的录像文件"))
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GREEN))),
Expand All @@ -48,20 +54,20 @@ private static Text red(String s){
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD))),
sizeLimitTooSmall = red("[SReplay] 大小限制不能小于10M"),
timeLimitTooSmall = red("[SReplay] 时间限制不能小于10s"),
recordingPaused = new LiteralText("[SReplay] ")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)))
recordingPaused = new LiteralText("[SReplay] $1: ")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("已暂停录制")),
recordingResumed = new LiteralText("[SReplay] ")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)))
recordingResumed = new LiteralText("[SReplay] $1: ")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("已继续开始录制")),
markerAdded = new LiteralText("[SReplay] 已在")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)))
markerAdded = new LiteralText("[SReplay] $1: 已在")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("添加标记"))
.append(new LiteralText("$2").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))),
markerRemoved = new LiteralText("[SReplay] 已在")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("$3").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))),
markerRemoved = new LiteralText("[SReplay] $1: 已在")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("删除标记"))
.append(new LiteralText("$2").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))),
.append(new LiteralText("$3").setStyle(new Style().setItalic(true).setColor(Formatting.GREEN))),
invalidMarkerId = red("[SReplay] 无效的标记序号"),
markerListTitle = new LiteralText("[SReplay] ")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GOLD)))
Expand All @@ -70,10 +76,10 @@ private static Text red(String s){
.append(new LiteralText("[删除]").setStyle(new Style().setColor(Formatting.GREEN)
.setClickEvent(new ClickEvent(Action.RUN_COMMAND, "/sreplay player $1 marker remove $2"))
)),
renamedFile = new LiteralText("[SReplay] 已将")
.append(new LiteralText("$1").setStyle(new Style().setColor(Formatting.GREEN)))
renamedFile = new LiteralText("[SReplay] $1: 已将")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GREEN)))
.append(new LiteralText("的文件名设置为"))
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GREEN))),
.append(new LiteralText("$3").setStyle(new Style().setColor(Formatting.GREEN))),
serverStarted = new LiteralText("[SReplay] 下载服务器已启动"),
serverStartFailed = red("[SReplay] 下载服务器启动失败:$1"),
serverStopped = new LiteralText("[SReplay] 下载服务器已停止"),
Expand All @@ -83,5 +89,13 @@ private static Text red(String s){
.setClickEvent(new ClickEvent(Action.OPEN_URL, "$1"))
.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new LiteralText("点击以下载").setStyle(new Style().setColor(Formatting.GRAY).setItalic(true))
))));
)))),
autoPaused = new LiteralText("[SReplay] $1: 附近无玩家,暂停录制"),
autoResumed = new LiteralText("[SReplay] $1: 附近有玩家,继续录制"),
setParam = new LiteralText("[SReplay] $1: 将")
.append(new LiteralText("$2").setStyle(new Style().setColor(Formatting.GOLD)))
.append(new LiteralText("的"))
.append(new LiteralText("$3").setStyle(new Style().setColor(Formatting.GREEN)))
.append(new LiteralText("值设置为"))
.append(new LiteralText("$4").setStyle(new Style().setColor(Formatting.GREEN)));
}
Loading

0 comments on commit f4c1beb

Please sign in to comment.