mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Run MCC at true 20 TPS
This commit is contained in:
parent
5f7e302213
commit
484133f07a
30 changed files with 1091 additions and 147 deletions
|
|
@ -142,7 +142,11 @@ mc-start "$VERSION" >/dev/null
|
||||||
wait_for_server_ready || fail "Server did not become ready"
|
wait_for_server_ready || fail "Server did not become ready"
|
||||||
|
|
||||||
echo "Starting MCC..."
|
echo "Starting MCC..."
|
||||||
mcc-run 25565 > "$MCC_LOG" 2>&1 &
|
mcc-run 25565 \
|
||||||
|
--advanced.terrainandmovements=true \
|
||||||
|
--advanced.inventoryhandling=true \
|
||||||
|
--advanced.entityhandling=true \
|
||||||
|
> "$MCC_LOG" 2>&1 &
|
||||||
MCC_PID=$!
|
MCC_PID=$!
|
||||||
|
|
||||||
wait_for_file_pattern "$MCC_LOG" "Server was successfully joined." "MCC join success" 90 || fail "MCC failed to join"
|
wait_for_file_pattern "$MCC_LOG" "Server was successfully joined." "MCC join success" 90 || fail "MCC failed to join"
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,9 @@ namespace MinecraftClient.ChatBots
|
||||||
Delay.min = Math.Max(1.0, Delay.min);
|
Delay.min = Math.Max(1.0, Delay.min);
|
||||||
Delay.max = Math.Max(1.0, Delay.max);
|
Delay.max = Math.Max(1.0, Delay.max);
|
||||||
|
|
||||||
Delay.min = Math.Min(int.MaxValue / 10, Delay.min);
|
double maxDelaySeconds = int.MaxValue / (double)Settings.ClientTicksPerSecond;
|
||||||
Delay.max = Math.Min(int.MaxValue / 10, Delay.max);
|
Delay.min = Math.Min(maxDelaySeconds, Delay.min);
|
||||||
|
Delay.max = Math.Min(maxDelaySeconds, Delay.max);
|
||||||
|
|
||||||
if (Delay.min > Delay.max)
|
if (Delay.min > Delay.max)
|
||||||
{
|
{
|
||||||
|
|
@ -83,7 +84,7 @@ namespace MinecraftClient.ChatBots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int count, nextrun = 50;
|
private int count, nextrun = Settings.DoubleToTick(5.0);
|
||||||
private bool previousSneakState = false;
|
private bool previousSneakState = false;
|
||||||
private readonly Random random = new();
|
private readonly Random random = new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ namespace MinecraftClient.ChatBots
|
||||||
if (Config.Cooldown_Time.Custom)
|
if (Config.Cooldown_Time.Custom)
|
||||||
{
|
{
|
||||||
attackCooldownSeconds = Config.Cooldown_Time.value;
|
attackCooldownSeconds = Config.Cooldown_Time.value;
|
||||||
attackCooldown = Convert.ToInt32(Math.Truncate(attackCooldownSeconds / 0.1) + 1);
|
attackCooldown = SecondsToAttackCooldownTicks(attackCooldownSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
attackHostile = Config.Attack_Hostile;
|
attackHostile = Config.Attack_Hostile;
|
||||||
|
|
@ -274,7 +274,7 @@ namespace MinecraftClient.ChatBots
|
||||||
serverTPS = GetServerTPS();
|
serverTPS = GetServerTPS();
|
||||||
attackSpeed = prop[attackSpeedKey];
|
attackSpeed = prop[attackSpeedKey];
|
||||||
attackCooldownSeconds = 1 / attackSpeed * (serverTPS / 20.0); // server tps will affect the cooldown
|
attackCooldownSeconds = 1 / attackSpeed * (serverTPS / 20.0); // server tps will affect the cooldown
|
||||||
attackCooldown = Convert.ToInt32(Math.Truncate(attackCooldownSeconds / 0.1) + 1);
|
attackCooldown = SecondsToAttackCooldownTicks(attackCooldownSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +288,13 @@ namespace MinecraftClient.ChatBots
|
||||||
serverTPS = tps;
|
serverTPS = tps;
|
||||||
// re-calculate attack speed
|
// re-calculate attack speed
|
||||||
attackCooldownSeconds = 1 / attackSpeed * (serverTPS / 20.0); // server tps will affect the cooldown
|
attackCooldownSeconds = 1 / attackSpeed * (serverTPS / 20.0); // server tps will affect the cooldown
|
||||||
attackCooldown = Convert.ToInt32(Math.Truncate(attackCooldownSeconds / 0.1) + 1);
|
attackCooldown = SecondsToAttackCooldownTicks(attackCooldownSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SecondsToAttackCooldownTicks(double seconds)
|
||||||
|
{
|
||||||
|
seconds = Math.Min(int.MaxValue / (double)Settings.ClientTicksPerSecond, seconds);
|
||||||
|
return Math.Max(1, (int)Math.Truncate(seconds * Settings.ClientTicksPerSecond) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -160,9 +160,9 @@ namespace MinecraftClient.ChatBots
|
||||||
private Recipe? recipeInUse;
|
private Recipe? recipeInUse;
|
||||||
private readonly List<ActionStep> actionSteps = new();
|
private readonly List<ActionStep> actionSteps = new();
|
||||||
|
|
||||||
private int updateDebounceValue = 2;
|
private int updateDebounceValue = Settings.DoubleToTick(0.2);
|
||||||
private int updateDebounce = 0;
|
private int updateDebounce = 0;
|
||||||
private readonly int updateTimeoutValue = 10;
|
private readonly int updateTimeoutValue = Settings.ClientTicksPerSecond;
|
||||||
private int updateTimeout = 0;
|
private int updateTimeout = 0;
|
||||||
private string timeoutAction = "unspecified";
|
private string timeoutAction = "unspecified";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace MinecraftClient.ChatBots
|
||||||
}
|
}
|
||||||
|
|
||||||
private int updateDebounce = 0;
|
private int updateDebounce = 0;
|
||||||
private readonly int updateDebounceValue = 2;
|
private readonly int updateDebounceValue = Settings.DoubleToTick(0.2);
|
||||||
private int inventoryUpdated = -1;
|
private int inventoryUpdated = -1;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ namespace MinecraftClient.ChatBots
|
||||||
Delay.min = Math.Max(0.1, Delay.min);
|
Delay.min = Math.Max(0.1, Delay.min);
|
||||||
Delay.max = Math.Max(0.1, Delay.max);
|
Delay.max = Math.Max(0.1, Delay.max);
|
||||||
|
|
||||||
Delay.min = Math.Min(int.MaxValue / 10, Delay.min);
|
double maxDelaySeconds = int.MaxValue / (double)Settings.ClientTicksPerSecond;
|
||||||
Delay.max = Math.Min(int.MaxValue / 10, Delay.max);
|
Delay.min = Math.Min(maxDelaySeconds, Delay.min);
|
||||||
|
Delay.max = Math.Min(maxDelaySeconds, Delay.max);
|
||||||
|
|
||||||
if (Delay.min > Delay.max)
|
if (Delay.min > Delay.max)
|
||||||
(Delay.min, Delay.max) = (Delay.max, Delay.min);
|
(Delay.min, Delay.max) = (Delay.max, Delay.min);
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ namespace MinecraftClient.ChatBots
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
// Poll every ~500ms (Update is called every ~100ms)
|
// Poll every ~500ms while the MCC main loop runs at 20 TPS.
|
||||||
if (++_tickCounter < 5)
|
if (++_tickCounter < Settings.DoubleToTick(0.5))
|
||||||
return;
|
return;
|
||||||
_tickCounter = 0;
|
_tickCounter = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ namespace MinecraftClient.ChatBots
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called on each MCC tick, around 10 times per second
|
/// Called on each MCC tick, around 20 times per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using Brigadier.NET.Builder;
|
using Brigadier.NET.Builder;
|
||||||
using MinecraftClient.CommandHandler;
|
using MinecraftClient.CommandHandler;
|
||||||
using MinecraftClient.CommandHandler.Patch;
|
using MinecraftClient.CommandHandler.Patch;
|
||||||
|
|
@ -86,7 +87,7 @@ namespace MinecraftClient.ChatBots
|
||||||
{
|
{
|
||||||
if (replay!.RecordRunning)
|
if (replay!.RecordRunning)
|
||||||
{
|
{
|
||||||
replay.CreateBackupReplay(@"replay_recordings\" + replay.GetReplayDefaultName());
|
replay.CreateBackupReplay(Path.Combine("replay_recordings", replay.GetReplayDefaultName()));
|
||||||
return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_replayCapture_created);
|
return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_replayCapture_created);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -127,7 +128,7 @@ namespace MinecraftClient.ChatBots
|
||||||
{
|
{
|
||||||
if (backupCounter <= 0)
|
if (backupCounter <= 0)
|
||||||
{
|
{
|
||||||
replay.CreateBackupReplay(@"recording_cache\REPLAY_BACKUP.mcpr");
|
replay.CreateBackupReplay(Path.Combine("recording_cache", "REPLAY_BACKUP.mcpr"));
|
||||||
backupCounter = Settings.DoubleToTick(Config.Backup_Interval);
|
backupCounter = Settings.DoubleToTick(Config.Backup_Interval);
|
||||||
}
|
}
|
||||||
else backupCounter--;
|
else backupCounter--;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace MinecraftClient.ChatBots
|
||||||
private string? file;
|
private string? file;
|
||||||
private string[] lines = Array.Empty<string>();
|
private string[] lines = Array.Empty<string>();
|
||||||
private string[] args = Array.Empty<string>();
|
private string[] args = Array.Empty<string>();
|
||||||
private int sleepticks = 10;
|
private int sleepticks = Settings.ClientTicksPerSecond;
|
||||||
private int nextline = 0;
|
private int nextline = 0;
|
||||||
private readonly string? owner;
|
private readonly string? owner;
|
||||||
private bool csharp;
|
private bool csharp;
|
||||||
|
|
@ -202,7 +202,7 @@ namespace MinecraftClient.ChatBots
|
||||||
switch (instruction_name.ToLower())
|
switch (instruction_name.ToLower())
|
||||||
{
|
{
|
||||||
case "wait":
|
case "wait":
|
||||||
int ticks = 10;
|
int ticks = Settings.ClientTicksPerSecond;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (instruction_line[5..].Contains("to", StringComparison.OrdinalIgnoreCase) ||
|
if (instruction_line[5..].Contains("to", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,8 @@ namespace MinecraftClient.ChatBots
|
||||||
private static bool firstlogin_done = false;
|
private static bool firstlogin_done = false;
|
||||||
|
|
||||||
private bool serverlogin_done = false;
|
private bool serverlogin_done = false;
|
||||||
private int verifytasks_timeleft = 10;
|
private int verifytasks_timeleft = Settings.ClientTicksPerSecond;
|
||||||
private readonly int verifytasks_delay = 10;
|
private readonly int verifytasks_delay = Settings.ClientTicksPerSecond;
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ namespace MinecraftClient
|
||||||
if (Config.ChatBot.Map.Enabled) { BotLoad(new Map()); }
|
if (Config.ChatBot.Map.Enabled) { BotLoad(new Map()); }
|
||||||
if (Config.ChatBot.PlayerListLogger.Enabled) { BotLoad(new PlayerListLogger()); }
|
if (Config.ChatBot.PlayerListLogger.Enabled) { BotLoad(new PlayerListLogger()); }
|
||||||
if (Config.ChatBot.RemoteControl.Enabled) { BotLoad(new RemoteControl()); }
|
if (Config.ChatBot.RemoteControl.Enabled) { BotLoad(new RemoteControl()); }
|
||||||
if (Config.ChatBot.ReplayCapture.Enabled && reload) { BotLoad(new ReplayCapture()); }
|
if (Config.ChatBot.ReplayCapture.Enabled) { BotLoad(new ReplayCapture()); }
|
||||||
if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); }
|
if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); }
|
||||||
if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); }
|
if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); }
|
||||||
if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); }
|
if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); }
|
||||||
|
|
@ -453,7 +453,7 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called ~10 times per second by the protocol handler
|
/// Called 20 times per second by the protocol handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnUpdate()
|
public void OnUpdate()
|
||||||
{
|
{
|
||||||
|
|
@ -510,37 +510,33 @@ namespace MinecraftClient
|
||||||
physicsInitialized = true;
|
physicsInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run 2 physics ticks per OnUpdate call (10 Hz * 2 = 20 TPS)
|
// Navigate pathfinding: set input based on current path
|
||||||
for (int tick = 0; tick < 2; tick++)
|
UpdatePathfindingInput();
|
||||||
{
|
|
||||||
// Navigate pathfinding: set input based on current path
|
|
||||||
UpdatePathfindingInput();
|
|
||||||
|
|
||||||
// Sync yaw/pitch if explicitly set (by commands/bots)
|
// Sync yaw/pitch if explicitly set (by commands/bots)
|
||||||
if (_yaw != null) playerPhysics.Yaw = _yaw.Value;
|
if (_yaw != null) playerPhysics.Yaw = _yaw.Value;
|
||||||
if (_pitch != null) playerPhysics.Pitch = _pitch.Value;
|
if (_pitch != null) playerPhysics.Pitch = _pitch.Value;
|
||||||
|
|
||||||
// Update environment flags (water, lava, climbable)
|
// Update environment flags (water, lava, climbable)
|
||||||
playerPhysics.UpdateEnvironment(world);
|
playerPhysics.UpdateEnvironment(world);
|
||||||
|
|
||||||
// Apply movement input
|
// Apply movement input
|
||||||
playerPhysics.ApplyInput(physicsInput);
|
playerPhysics.ApplyInput(physicsInput);
|
||||||
|
|
||||||
// Run one physics tick
|
// Run one physics tick
|
||||||
playerPhysics.Tick(world);
|
playerPhysics.Tick(world);
|
||||||
|
|
||||||
// Sync back to MCC location
|
// Sync back to MCC location
|
||||||
location = new Location(
|
location = new Location(
|
||||||
playerPhysics.Position.X,
|
playerPhysics.Position.X,
|
||||||
playerPhysics.Position.Y,
|
playerPhysics.Position.Y,
|
||||||
playerPhysics.Position.Z);
|
playerPhysics.Position.Z);
|
||||||
|
|
||||||
playerYaw = _yaw ?? playerYaw;
|
playerYaw = _yaw ?? playerYaw;
|
||||||
playerPitch = _pitch ?? playerPitch;
|
playerPitch = _pitch ?? playerPitch;
|
||||||
|
|
||||||
// Send position packet
|
// Send position packet
|
||||||
handler.SendLocationUpdate(location, playerPhysics.OnGround, _yaw, _pitch);
|
handler.SendLocationUpdate(location, playerPhysics.OnGround, playerPhysics.HorizontalCollision, _yaw, _pitch);
|
||||||
}
|
|
||||||
|
|
||||||
_yaw = null;
|
_yaw = null;
|
||||||
_pitch = null;
|
_pitch = null;
|
||||||
|
|
@ -1350,7 +1346,7 @@ namespace MinecraftClient
|
||||||
{
|
{
|
||||||
// 1-step path to the desired location without checking anything
|
// 1-step path to the desired location without checking anything
|
||||||
UpdateLocation(goal, goal); // Update yaw and pitch to look at next step
|
UpdateLocation(goal, goal); // Update yaw and pitch to look at next step
|
||||||
handler.SendLocationUpdate(goal, Movement.IsOnGround(world, goal), _yaw, _pitch);
|
handler.SendLocationUpdate(goal, Movement.IsOnGround(world, goal), false, _yaw, _pitch);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -2421,7 +2417,7 @@ namespace MinecraftClient
|
||||||
if (lookAtBlock)
|
if (lookAtBlock)
|
||||||
{
|
{
|
||||||
UpdateLocation(GetCurrentLocation(), location.ToCenter());
|
UpdateLocation(GetCurrentLocation(), location.ToCenter());
|
||||||
handler.SendLocationUpdate(GetCurrentLocation(), Movement.IsOnGround(world, GetCurrentLocation()), _yaw, _pitch);
|
handler.SendLocationUpdate(GetCurrentLocation(), Movement.IsOnGround(world, GetCurrentLocation()), false, _yaw, _pitch);
|
||||||
}
|
}
|
||||||
return handler.SendPlayerBlockPlacement((int)hand, location, blockFace, sequenceId++);
|
return handler.SendPlayerBlockPlacement((int)hand, location, blockFace, sequenceId++);
|
||||||
});
|
});
|
||||||
|
|
@ -3562,7 +3558,7 @@ namespace MinecraftClient
|
||||||
if (Config.Main.Advanced.AutoRespawn)
|
if (Config.Main.Advanced.AutoRespawn)
|
||||||
{
|
{
|
||||||
Log.Info(Translations.mcc_player_dead_respawn);
|
Log.Info(Translations.mcc_player_dead_respawn);
|
||||||
respawnTicks = 10;
|
respawnTicks = Settings.ClientTicksPerSecond;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,15 @@
|
||||||
<Compile Remove="config\ChatBots\TreeFarmer.cs" />
|
<Compile Remove="config\ChatBots\TreeFarmer.cs" />
|
||||||
<Compile Remove="config\ChatBots\VkMessager.cs" />
|
<Compile Remove="config\ChatBots\VkMessager.cs" />
|
||||||
<Compile Remove="config\sample-script-extended.cs" />
|
<Compile Remove="config\sample-script-extended.cs" />
|
||||||
|
<Compile Remove="config\sample-script-packet-capture.cs" />
|
||||||
<Compile Remove="config\sample-script-pm-forwarder.cs" />
|
<Compile Remove="config\sample-script-pm-forwarder.cs" />
|
||||||
<Compile Remove="config\sample-script-random-command.cs" />
|
<Compile Remove="config\sample-script-random-command.cs" />
|
||||||
|
<Compile Remove="config\sample-script-tick-counter.cs" />
|
||||||
<Compile Remove="config\sample-script-with-chatbot.cs" />
|
<Compile Remove="config\sample-script-with-chatbot.cs" />
|
||||||
<Compile Remove="config\sample-script-with-http-request.cs" />
|
<Compile Remove="config\sample-script-with-http-request.cs" />
|
||||||
<Compile Remove="config\sample-script-with-task.cs" />
|
<Compile Remove="config\sample-script-with-task.cs" />
|
||||||
<Compile Remove="config\sample-script-with-world-access.cs" />
|
<Compile Remove="config\sample-script-with-world-access.cs" />
|
||||||
|
<Compile Remove="config\sample-script-packet-capture.cs" />
|
||||||
<Compile Remove="config\sample-script.cs" />
|
<Compile Remove="config\sample-script.cs" />
|
||||||
<Compile Remove="config\ChatBots\MineCube.cs" />
|
<Compile Remove="config\ChatBots\MineCube.cs" />
|
||||||
<Compile Remove="config\ChatBots\SugarCaneFarmer.cs" />
|
<Compile Remove="config\ChatBots\SugarCaneFarmer.cs" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
@ -70,24 +71,41 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
private void Updater(object? o)
|
private void Updater(object? o)
|
||||||
{
|
{
|
||||||
if (((CancellationToken)o!).IsCancellationRequested)
|
var cancelToken = (CancellationToken)o!;
|
||||||
|
|
||||||
|
if (cancelToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!((CancellationToken)o!).IsCancellationRequested)
|
Stopwatch stopWatch = Stopwatch.StartNew();
|
||||||
|
long nextUpdateDue = 0;
|
||||||
|
|
||||||
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
do
|
cancelToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
long elapsedMilliseconds = stopWatch.ElapsedMilliseconds;
|
||||||
|
while (elapsedMilliseconds >= nextUpdateDue)
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
if (!Update())
|
||||||
} while (Update());
|
return;
|
||||||
|
|
||||||
|
nextUpdateDue += ClientTickIntervalMilliseconds;
|
||||||
|
elapsedMilliseconds = stopWatch.ElapsedMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
long sleepLength = nextUpdateDue - stopWatch.ElapsedMilliseconds;
|
||||||
|
if (sleepLength > 1)
|
||||||
|
Thread.Sleep((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (System.IO.IOException) { }
|
catch (System.IO.IOException) { }
|
||||||
catch (SocketException) { }
|
catch (SocketException) { }
|
||||||
catch (ObjectDisposedException) { }
|
catch (ObjectDisposedException) { }
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
|
||||||
if (((CancellationToken)o!).IsCancellationRequested)
|
if (cancelToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
|
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
|
||||||
|
|
@ -737,7 +755,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
return false; //Currently not implemented
|
return false; //Currently not implemented
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SendLocationUpdate(Location location, bool onGround, float? yaw, float? pitch)
|
public bool SendLocationUpdate(Location location, bool onGround, bool horizontalCollision, float? yaw, float? pitch)
|
||||||
{
|
{
|
||||||
return false; //Currently not implemented
|
return false; //Currently not implemented
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
private double lastSentX, lastSentY, lastSentZ;
|
private double lastSentX, lastSentY, lastSentZ;
|
||||||
private float lastSentYaw, lastSentPitch;
|
private float lastSentYaw, lastSentPitch;
|
||||||
private bool lastSentOnGround;
|
private bool lastSentOnGround;
|
||||||
|
private bool lastSentHorizontalCollision;
|
||||||
private int positionReminder;
|
private int positionReminder;
|
||||||
private long chunkBatchStartTime;
|
private long chunkBatchStartTime;
|
||||||
private double aggregatedNanosPerChunk = 2000000.0;
|
private double aggregatedNanosPerChunk = 2000000.0;
|
||||||
|
|
@ -286,28 +287,30 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Stopwatch stopWatch = new();
|
Stopwatch stopWatch = Stopwatch.StartNew();
|
||||||
|
long nextUpdateDue = 0;
|
||||||
while (!packetQueue.IsAddingCompleted)
|
while (!packetQueue.IsAddingCompleted)
|
||||||
{
|
{
|
||||||
cancelToken.ThrowIfCancellationRequested();
|
cancelToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
handler.OnUpdate();
|
long elapsedMilliseconds = stopWatch.ElapsedMilliseconds;
|
||||||
stopWatch.Restart();
|
while (elapsedMilliseconds >= nextUpdateDue)
|
||||||
|
{
|
||||||
|
handler.OnUpdate();
|
||||||
|
nextUpdateDue += ClientTickIntervalMilliseconds;
|
||||||
|
elapsedMilliseconds = stopWatch.ElapsedMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
while (packetQueue.TryTake(out var packetInfo))
|
if (packetQueue.TryTake(out var packetInfo, 1))
|
||||||
{
|
{
|
||||||
var (packetId, packetData) = packetInfo;
|
var (packetId, packetData) = packetInfo;
|
||||||
HandlePacket(packetId, packetData);
|
HandlePacket(packetId, packetData);
|
||||||
|
continue;
|
||||||
if (stopWatch.Elapsed.Milliseconds < 100) continue;
|
|
||||||
|
|
||||||
handler.OnUpdate();
|
|
||||||
stopWatch.Restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sleepLength = 100 - stopWatch.Elapsed.Milliseconds;
|
long sleepLength = nextUpdateDue - stopWatch.ElapsedMilliseconds;
|
||||||
if (sleepLength > 0)
|
if (sleepLength > 1)
|
||||||
Thread.Sleep(sleepLength);
|
Thread.Sleep((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
|
|
@ -1505,10 +1508,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
if (Config.Main.Advanced.TemporaryFixBadpacket)
|
if (Config.Main.Advanced.TemporaryFixBadpacket)
|
||||||
{
|
{
|
||||||
SendLocationUpdate(location, true, yaw, pitch, true);
|
SendLocationUpdate(location, true, false, yaw, pitch, true);
|
||||||
|
|
||||||
if (teleportId == 1)
|
if (teleportId == 1)
|
||||||
SendLocationUpdate(location, true, yaw, pitch, true);
|
SendLocationUpdate(location, true, false, yaw, pitch, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -4109,93 +4112,156 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="location">The new location of the player</param>
|
/// <param name="location">The new location of the player</param>
|
||||||
/// <param name="onGround">True if the player is on the ground</param>
|
/// <param name="onGround">True if the player is on the ground</param>
|
||||||
|
/// <param name="horizontalCollision">True if the player is colliding horizontally</param>
|
||||||
/// <param name="yaw">Optional new yaw for updating player look</param>
|
/// <param name="yaw">Optional new yaw for updating player look</param>
|
||||||
/// <param name="pitch">Optional new pitch for updating player look</param>
|
/// <param name="pitch">Optional new pitch for updating player look</param>
|
||||||
/// <returns>True if the location update was successfully sent</returns>
|
/// <returns>True if the location update was successfully sent</returns>
|
||||||
public bool SendLocationUpdate(Location location, bool onGround, float? yaw, float? pitch)
|
public bool SendLocationUpdate(Location location, bool onGround, bool horizontalCollision, float? yaw, float? pitch)
|
||||||
{
|
{
|
||||||
return SendLocationUpdate(location, onGround, yaw, pitch, true);
|
return SendLocationUpdate(location, onGround, horizontalCollision, yaw, pitch, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null,
|
public bool SendLocationUpdate(Location location, bool onGround, bool horizontalCollision, float? yaw = null, float? pitch = null,
|
||||||
bool forceUpdate = false)
|
bool forceUpdate = false)
|
||||||
{
|
{
|
||||||
if (handler.GetTerrainEnabled())
|
if (handler.GetTerrainEnabled())
|
||||||
{
|
{
|
||||||
// Vanilla-like packet selection (LocalPlayer.sendPosition):
|
bool legacyMovementCadence = protocolVersion < MC_1_9_Version;
|
||||||
// Send position if delta > (2e-4)^2 or every 20 ticks
|
bool supportsHorizontalCollision = protocolVersion >= MC_1_21_5_Version;
|
||||||
// Send rotation if yaw/pitch changed
|
int positionReminderInterval = ClientTicksPerSecond;
|
||||||
// Send StatusOnly if only onGround changed
|
|
||||||
|
|
||||||
double dx = location.X - lastSentX;
|
double dx = location.X - lastSentX;
|
||||||
double dy = location.Y - lastSentY;
|
double dy = location.Y - lastSentY;
|
||||||
double dz = location.Z - lastSentZ;
|
double dz = location.Z - lastSentZ;
|
||||||
double distSqr = dx * dx + dy * dy + dz * dz;
|
double distSqr = dx * dx + dy * dy + dz * dz;
|
||||||
|
|
||||||
bool positionChanged = distSqr > 4.0E-8 || positionReminder >= 20;
|
|
||||||
bool rotationChanged = false;
|
bool rotationChanged = false;
|
||||||
if (yaw.HasValue && pitch.HasValue)
|
if (yaw.HasValue && pitch.HasValue)
|
||||||
rotationChanged = forceUpdate || yaw.Value != lastSentYaw || pitch.Value != lastSentPitch;
|
rotationChanged = forceUpdate || yaw.Value != lastSentYaw || pitch.Value != lastSentPitch;
|
||||||
bool groundChanged = onGround != lastSentOnGround;
|
|
||||||
|
|
||||||
positionReminder++;
|
positionReminder++;
|
||||||
|
|
||||||
if (!positionChanged && !rotationChanged && !groundChanged)
|
|
||||||
return true; // Nothing to send
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PacketTypesOut packetType;
|
PacketTypesOut packetType;
|
||||||
byte[] payload;
|
byte[] payload;
|
||||||
byte flags = (byte)(onGround ? 1 : 0);
|
byte flags = (byte)(onGround ? 1 : 0);
|
||||||
|
bool positionChanged;
|
||||||
|
|
||||||
if (positionChanged && rotationChanged && yaw.HasValue && pitch.HasValue)
|
if (legacyMovementCadence)
|
||||||
{
|
{
|
||||||
packetType = PacketTypesOut.PlayerPositionAndRotation;
|
// 1.7.2-1.8.9 mirrors EntityPlayerSP#onUpdateWalkingPlayer:
|
||||||
payload = dataTypes.ConcatBytes(
|
// send an idle PlayerMovement packet every client tick and force
|
||||||
dataTypes.GetDouble(location.X),
|
// a position refresh every 20 ticks even if the player is standing still.
|
||||||
dataTypes.GetDouble(location.Y),
|
positionChanged = distSqr > 9.0E-4 || positionReminder >= positionReminderInterval;
|
||||||
protocolVersion < MC_1_8_Version
|
|
||||||
? dataTypes.GetDouble(location.Y + 1.62)
|
if (positionChanged && rotationChanged && yaw.HasValue && pitch.HasValue)
|
||||||
: Array.Empty<byte>(),
|
{
|
||||||
dataTypes.GetDouble(location.Z),
|
packetType = PacketTypesOut.PlayerPositionAndRotation;
|
||||||
dataTypes.GetFloat(yaw.Value),
|
payload = dataTypes.ConcatBytes(
|
||||||
dataTypes.GetFloat(pitch.Value),
|
dataTypes.GetDouble(location.X),
|
||||||
new[] { flags });
|
dataTypes.GetDouble(location.Y),
|
||||||
lastSentYaw = yaw.Value;
|
protocolVersion < MC_1_8_Version
|
||||||
lastSentPitch = pitch.Value;
|
? dataTypes.GetDouble(location.Y + 1.62)
|
||||||
LastYaw = yaw.Value;
|
: Array.Empty<byte>(),
|
||||||
LastPitch = pitch.Value;
|
dataTypes.GetDouble(location.Z),
|
||||||
}
|
dataTypes.GetFloat(yaw.Value),
|
||||||
else if (positionChanged)
|
dataTypes.GetFloat(pitch.Value),
|
||||||
{
|
new[] { flags });
|
||||||
packetType = PacketTypesOut.PlayerPosition;
|
lastSentYaw = yaw.Value;
|
||||||
payload = dataTypes.ConcatBytes(
|
lastSentPitch = pitch.Value;
|
||||||
dataTypes.GetDouble(location.X),
|
LastYaw = yaw.Value;
|
||||||
dataTypes.GetDouble(location.Y),
|
LastPitch = pitch.Value;
|
||||||
protocolVersion < MC_1_8_Version
|
}
|
||||||
? dataTypes.GetDouble(location.Y + 1.62)
|
else if (positionChanged)
|
||||||
: Array.Empty<byte>(),
|
{
|
||||||
dataTypes.GetDouble(location.Z),
|
packetType = PacketTypesOut.PlayerPosition;
|
||||||
new[] { flags });
|
payload = dataTypes.ConcatBytes(
|
||||||
}
|
dataTypes.GetDouble(location.X),
|
||||||
else if (rotationChanged && yaw.HasValue && pitch.HasValue)
|
dataTypes.GetDouble(location.Y),
|
||||||
{
|
protocolVersion < MC_1_8_Version
|
||||||
packetType = PacketTypesOut.PlayerRotation;
|
? dataTypes.GetDouble(location.Y + 1.62)
|
||||||
payload = dataTypes.ConcatBytes(
|
: Array.Empty<byte>(),
|
||||||
dataTypes.GetFloat(yaw.Value),
|
dataTypes.GetDouble(location.Z),
|
||||||
dataTypes.GetFloat(pitch.Value),
|
new[] { flags });
|
||||||
new[] { flags });
|
}
|
||||||
lastSentYaw = yaw.Value;
|
else if (rotationChanged && yaw.HasValue && pitch.HasValue)
|
||||||
lastSentPitch = pitch.Value;
|
{
|
||||||
LastYaw = yaw.Value;
|
packetType = PacketTypesOut.PlayerRotation;
|
||||||
LastPitch = pitch.Value;
|
payload = dataTypes.ConcatBytes(
|
||||||
|
dataTypes.GetFloat(yaw.Value),
|
||||||
|
dataTypes.GetFloat(pitch.Value),
|
||||||
|
new[] { flags });
|
||||||
|
lastSentYaw = yaw.Value;
|
||||||
|
lastSentPitch = pitch.Value;
|
||||||
|
LastYaw = yaw.Value;
|
||||||
|
LastPitch = pitch.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
packetType = PacketTypesOut.PlayerMovement;
|
||||||
|
payload = new[] { flags };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Only onGround changed — send StatusOnly (PlayerMovement)
|
positionChanged = distSqr > 4.0E-8 || positionReminder >= positionReminderInterval;
|
||||||
packetType = PacketTypesOut.PlayerMovement;
|
bool movementStateChanged = onGround != lastSentOnGround
|
||||||
payload = new[] { flags };
|
|| (supportsHorizontalCollision && horizontalCollision != lastSentHorizontalCollision);
|
||||||
|
|
||||||
|
if (!positionChanged && !rotationChanged && !movementStateChanged)
|
||||||
|
return true; // Nothing to send
|
||||||
|
|
||||||
|
if (supportsHorizontalCollision && horizontalCollision)
|
||||||
|
flags |= 0x2;
|
||||||
|
|
||||||
|
if (positionChanged && rotationChanged && yaw.HasValue && pitch.HasValue)
|
||||||
|
{
|
||||||
|
packetType = PacketTypesOut.PlayerPositionAndRotation;
|
||||||
|
payload = dataTypes.ConcatBytes(
|
||||||
|
dataTypes.GetDouble(location.X),
|
||||||
|
dataTypes.GetDouble(location.Y),
|
||||||
|
protocolVersion < MC_1_8_Version
|
||||||
|
? dataTypes.GetDouble(location.Y + 1.62)
|
||||||
|
: Array.Empty<byte>(),
|
||||||
|
dataTypes.GetDouble(location.Z),
|
||||||
|
dataTypes.GetFloat(yaw.Value),
|
||||||
|
dataTypes.GetFloat(pitch.Value),
|
||||||
|
new[] { flags });
|
||||||
|
lastSentYaw = yaw.Value;
|
||||||
|
lastSentPitch = pitch.Value;
|
||||||
|
LastYaw = yaw.Value;
|
||||||
|
LastPitch = pitch.Value;
|
||||||
|
}
|
||||||
|
else if (positionChanged)
|
||||||
|
{
|
||||||
|
packetType = PacketTypesOut.PlayerPosition;
|
||||||
|
payload = dataTypes.ConcatBytes(
|
||||||
|
dataTypes.GetDouble(location.X),
|
||||||
|
dataTypes.GetDouble(location.Y),
|
||||||
|
protocolVersion < MC_1_8_Version
|
||||||
|
? dataTypes.GetDouble(location.Y + 1.62)
|
||||||
|
: Array.Empty<byte>(),
|
||||||
|
dataTypes.GetDouble(location.Z),
|
||||||
|
new[] { flags });
|
||||||
|
}
|
||||||
|
else if (rotationChanged && yaw.HasValue && pitch.HasValue)
|
||||||
|
{
|
||||||
|
packetType = PacketTypesOut.PlayerRotation;
|
||||||
|
payload = dataTypes.ConcatBytes(
|
||||||
|
dataTypes.GetFloat(yaw.Value),
|
||||||
|
dataTypes.GetFloat(pitch.Value),
|
||||||
|
new[] { flags });
|
||||||
|
lastSentYaw = yaw.Value;
|
||||||
|
lastSentPitch = pitch.Value;
|
||||||
|
LastYaw = yaw.Value;
|
||||||
|
LastPitch = pitch.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
packetType = PacketTypesOut.PlayerMovement;
|
||||||
|
payload = new[] { flags };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positionChanged)
|
if (positionChanged)
|
||||||
|
|
@ -4206,6 +4272,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
positionReminder = 0;
|
positionReminder = 0;
|
||||||
}
|
}
|
||||||
lastSentOnGround = onGround;
|
lastSentOnGround = onGround;
|
||||||
|
lastSentHorizontalCollision = horizontalCollision;
|
||||||
|
|
||||||
SendPacket(packetType, payload);
|
SendPacket(packetType, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,11 @@ namespace MinecraftClient.Protocol
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="location">The new location</param>
|
/// <param name="location">The new location</param>
|
||||||
/// <param name="onGround">True if the player is on the ground</param>
|
/// <param name="onGround">True if the player is on the ground</param>
|
||||||
|
/// <param name="horizontalCollision">True if the player is colliding horizontally</param>
|
||||||
/// <param name="yaw">The new yaw (optional)</param>
|
/// <param name="yaw">The new yaw (optional)</param>
|
||||||
/// <param name="pitch">The new pitch (optional)</param>
|
/// <param name="pitch">The new pitch (optional)</param>
|
||||||
/// <returns>True if packet was successfully sent</returns>
|
/// <returns>True if packet was successfully sent</returns>
|
||||||
bool SendLocationUpdate(Location location, bool onGround, float? yaw, float? pitch);
|
bool SendLocationUpdate(Location location, bool onGround, bool horizontalCollision, float? yaw, float? pitch);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a plugin channel packet to the server.
|
/// Send a plugin channel packet to the server.
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ namespace MinecraftClient.Protocol
|
||||||
void OnConnectionLost(ChatBot.DisconnectReason reason, string message);
|
void OnConnectionLost(ChatBot.DisconnectReason reason, string message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called ~10 times per second (10 ticks per second)
|
/// Called 20 times per second (20 ticks per second)
|
||||||
/// Useful for updating bots in other parts of the program
|
/// Useful for updating bots in other parts of the program
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void OnUpdate();
|
void OnUpdate();
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,9 @@ namespace MinecraftClient.Protocol
|
||||||
/// <param name="isInbound"></param>
|
/// <param name="isInbound"></param>
|
||||||
public void AddPacket(int packetID, IEnumerable<byte> packetData, bool isLogin, bool isInbound)
|
public void AddPacket(int packetID, IEnumerable<byte> packetData, bool isLogin, bool isInbound)
|
||||||
{
|
{
|
||||||
|
if (cleanedUp || prepareCleanUp)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (isInbound)
|
if (isInbound)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ namespace MinecraftClient.Scripting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will be called every ~100ms.
|
/// Will be called every client tick (~50ms at 20 TPS).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="Update"/> method can be overridden by child class so need an extra update method
|
/// <see cref="Update"/> method can be overridden by child class so need an extra update method
|
||||||
|
|
@ -117,7 +117,7 @@ namespace MinecraftClient.Scripting
|
||||||
public virtual void AfterGameJoined() { }
|
public virtual void AfterGameJoined() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will be called every ~100ms (10fps) if loaded in MinecraftCom
|
/// Will be called every client tick (~50ms, 20 TPS) if loaded in MinecraftCom
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Update() { }
|
public virtual void Update() { }
|
||||||
|
|
||||||
|
|
@ -1673,11 +1673,11 @@ namespace MinecraftClient.Scripting
|
||||||
/// Schedule a task to run on the main thread, and do not wait for completion
|
/// Schedule a task to run on the main thread, and do not wait for completion
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="task">Task to run</param>
|
/// <param name="task">Task to run</param>
|
||||||
/// <param name="delayTicks">Run the task after X ticks (1 tick delay = ~100ms). 0 for no delay</param>
|
/// <param name="delayTicks">Run the task after X ticks (1 tick delay = ~50ms at 20 TPS). 0 for no delay</param>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// <example>InvokeOnMainThread(methodThatReturnsNothing, 10);</example>
|
/// <example>InvokeOnMainThread(methodThatReturnsNothing, 20);</example>
|
||||||
/// <example>InvokeOnMainThread(() => methodThatReturnsNothing(argument), 10);</example>
|
/// <example>InvokeOnMainThread(() => methodThatReturnsNothing(argument), 20);</example>
|
||||||
/// <example>InvokeOnMainThread(() => { yourCode(); }, 10);</example>
|
/// <example>InvokeOnMainThread(() => { yourCode(); }, 20);</example>
|
||||||
/// </example>
|
/// </example>
|
||||||
protected void ScheduleOnMainThread(Action task, int delayTicks = 0)
|
protected void ScheduleOnMainThread(Action task, int delayTicks = 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ namespace MinecraftClient
|
||||||
public const string TranslationsFile_Website_Download = "https://resources.download.minecraft.net";
|
public const string TranslationsFile_Website_Download = "https://resources.download.minecraft.net";
|
||||||
|
|
||||||
public const string TranslationProjectUrl = "https://crwd.in/minecraft-console-client";
|
public const string TranslationProjectUrl = "https://crwd.in/minecraft-console-client";
|
||||||
|
public const int ClientTicksPerSecond = 20;
|
||||||
|
public const int ClientTickIntervalMilliseconds = 1000 / ClientTicksPerSecond;
|
||||||
|
|
||||||
public static GlobalConfig Config = new();
|
public static GlobalConfig Config = new();
|
||||||
|
|
||||||
|
|
@ -1928,8 +1930,8 @@ namespace MinecraftClient
|
||||||
|
|
||||||
public static int DoubleToTick(double time)
|
public static int DoubleToTick(double time)
|
||||||
{
|
{
|
||||||
time = Math.Min(int.MaxValue / 10, time);
|
time = Math.Min(int.MaxValue / (double)ClientTicksPerSecond, time);
|
||||||
return (int)Math.Round(time * 10);
|
return (int)Math.Round(time * ClientTicksPerSecond);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
178
MinecraftClient/config/sample-script-packet-capture.cs
Normal file
178
MinecraftClient/config/sample-script-packet-capture.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
//MCCScript 1.0
|
||||||
|
|
||||||
|
MCC.LoadBot(new PacketCadenceCaptureBot());
|
||||||
|
|
||||||
|
//MCCScript Extensions
|
||||||
|
|
||||||
|
public class PacketCadenceCaptureBot : ChatBot
|
||||||
|
{
|
||||||
|
private const int CaptureDurationSeconds = 5;
|
||||||
|
private const int CaptureDurationTicks = CaptureDurationSeconds * 20;
|
||||||
|
|
||||||
|
private readonly object _countsLock = new();
|
||||||
|
private readonly Dictionary<string, int> _counts = new()
|
||||||
|
{
|
||||||
|
{ "PlayerMovement", 0 },
|
||||||
|
{ "PlayerPosition", 0 },
|
||||||
|
{ "PlayerPositionAndRotation", 0 },
|
||||||
|
{ "PlayerRotation", 0 }
|
||||||
|
};
|
||||||
|
private readonly Dictionary<int, int> _rawOutgoingCounts = new();
|
||||||
|
|
||||||
|
private bool _captureStarted;
|
||||||
|
private bool _captureSupported;
|
||||||
|
private bool _networkPacketEventEnabled;
|
||||||
|
private int _ticksRemaining;
|
||||||
|
private int _playerMovementPacketId = -1;
|
||||||
|
private int _playerPositionPacketId = -1;
|
||||||
|
private int _playerPositionAndRotationPacketId = -1;
|
||||||
|
private int _playerRotationPacketId = -1;
|
||||||
|
private string _profileName = "unsupported";
|
||||||
|
|
||||||
|
public override void AfterGameJoined()
|
||||||
|
{
|
||||||
|
int protocolVersion = GetProtocolVersion();
|
||||||
|
_captureSupported = TryConfigureProfile(protocolVersion);
|
||||||
|
|
||||||
|
LogToConsole($"Packet cadence profile: {_profileName} (protocol v{protocolVersion})");
|
||||||
|
|
||||||
|
if (!_captureSupported)
|
||||||
|
{
|
||||||
|
LogToConsole("Packet cadence capture does not know the outgoing movement IDs for this protocol.");
|
||||||
|
UnloadBot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetNetworkPacketEventEnabled(true);
|
||||||
|
_networkPacketEventEnabled = true;
|
||||||
|
|
||||||
|
_captureStarted = true;
|
||||||
|
_ticksRemaining = CaptureDurationTicks;
|
||||||
|
LogToConsole($"Capturing outgoing movement packets for {CaptureDurationSeconds} seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
if (!_captureStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (--_ticksRemaining > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FinishCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinishCapture()
|
||||||
|
{
|
||||||
|
if (!_captureStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_captureStarted = false;
|
||||||
|
|
||||||
|
int movementCount;
|
||||||
|
int positionCount;
|
||||||
|
int positionAndRotationCount;
|
||||||
|
int rotationCount;
|
||||||
|
|
||||||
|
lock (_countsLock)
|
||||||
|
{
|
||||||
|
movementCount = _counts["PlayerMovement"];
|
||||||
|
positionCount = _counts["PlayerPosition"];
|
||||||
|
positionAndRotationCount = _counts["PlayerPositionAndRotation"];
|
||||||
|
rotationCount = _counts["PlayerRotation"];
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalPackets = movementCount + positionCount + positionAndRotationCount + rotationCount;
|
||||||
|
|
||||||
|
LogToConsole($"Packet cadence summary ({_profileName}): total={totalPackets}, " +
|
||||||
|
$"movement={movementCount}, position={positionCount}, " +
|
||||||
|
$"posrot={positionAndRotationCount}, rotation={rotationCount}");
|
||||||
|
|
||||||
|
if (totalPackets == 0)
|
||||||
|
{
|
||||||
|
string rawSummary;
|
||||||
|
lock (_countsLock)
|
||||||
|
{
|
||||||
|
var entries = new List<string>();
|
||||||
|
foreach (var entry in _rawOutgoingCounts.OrderBy(entry => entry.Key))
|
||||||
|
entries.Add($"0x{entry.Key:X2}={entry.Value}");
|
||||||
|
rawSummary = entries.Count > 0 ? string.Join(", ", entries) : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
LogToConsole($"Packet cadence raw outbound IDs: {rawSummary}");
|
||||||
|
}
|
||||||
|
|
||||||
|
UnloadBot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkPacket(int packetID, List<byte> packetData, bool isLogin, bool isInbound)
|
||||||
|
{
|
||||||
|
if (!_captureStarted || isLogin || isInbound)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_countsLock)
|
||||||
|
{
|
||||||
|
_rawOutgoingCounts.TryGetValue(packetID, out int rawCount);
|
||||||
|
_rawOutgoingCounts[packetID] = rawCount + 1;
|
||||||
|
|
||||||
|
if (packetID == _playerMovementPacketId)
|
||||||
|
_counts["PlayerMovement"]++;
|
||||||
|
else if (packetID == _playerPositionPacketId)
|
||||||
|
_counts["PlayerPosition"]++;
|
||||||
|
else if (packetID == _playerPositionAndRotationPacketId)
|
||||||
|
_counts["PlayerPositionAndRotation"]++;
|
||||||
|
else if (packetID == _playerRotationPacketId)
|
||||||
|
_counts["PlayerRotation"]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUnload()
|
||||||
|
{
|
||||||
|
if (!_networkPacketEventEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetNetworkPacketEventEnabled(false);
|
||||||
|
_networkPacketEventEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryConfigureProfile(int protocolVersion)
|
||||||
|
{
|
||||||
|
switch (protocolVersion)
|
||||||
|
{
|
||||||
|
case 47:
|
||||||
|
_profileName = "1.8/1.8.9";
|
||||||
|
_playerMovementPacketId = 0x03;
|
||||||
|
_playerPositionPacketId = 0x04;
|
||||||
|
_playerPositionAndRotationPacketId = 0x06;
|
||||||
|
_playerRotationPacketId = 0x05;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 766:
|
||||||
|
case 767:
|
||||||
|
case 768:
|
||||||
|
case 769:
|
||||||
|
case 770:
|
||||||
|
case 771:
|
||||||
|
case 772:
|
||||||
|
_profileName = "1.20.6-1.21.8";
|
||||||
|
_playerMovementPacketId = 0x1D;
|
||||||
|
_playerPositionPacketId = 0x1A;
|
||||||
|
_playerPositionAndRotationPacketId = 0x1B;
|
||||||
|
_playerRotationPacketId = 0x1C;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 773:
|
||||||
|
case 774:
|
||||||
|
case 775:
|
||||||
|
_profileName = "1.21.9+";
|
||||||
|
_playerMovementPacketId = 0x20;
|
||||||
|
_playerPositionPacketId = 0x1D;
|
||||||
|
_playerPositionAndRotationPacketId = 0x1E;
|
||||||
|
_playerRotationPacketId = 0x1F;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
MinecraftClient/config/sample-script-tick-counter.cs
Normal file
40
MinecraftClient/config/sample-script-tick-counter.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
//MCCScript 1.0
|
||||||
|
|
||||||
|
MCC.LoadBot(new TickCounterBot());
|
||||||
|
|
||||||
|
//MCCScript Extensions
|
||||||
|
|
||||||
|
public class TickCounterBot : ChatBot
|
||||||
|
{
|
||||||
|
private const int CaptureDurationSeconds = 5;
|
||||||
|
|
||||||
|
private DateTime _captureEndsAt = DateTime.MaxValue;
|
||||||
|
private bool _captureStarted;
|
||||||
|
private int _updateCount;
|
||||||
|
|
||||||
|
public override void AfterGameJoined()
|
||||||
|
{
|
||||||
|
_captureEndsAt = DateTime.UtcNow.AddSeconds(CaptureDurationSeconds);
|
||||||
|
_captureStarted = true;
|
||||||
|
_updateCount = 0;
|
||||||
|
LogToConsole($"Counting MCC update ticks for {CaptureDurationSeconds} seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
if (!_captureStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_updateCount++;
|
||||||
|
|
||||||
|
if (DateTime.UtcNow < _captureEndsAt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_captureStarted = false;
|
||||||
|
|
||||||
|
double ticksPerSecond = _updateCount / (double)CaptureDurationSeconds;
|
||||||
|
LogToConsole($"Tick counter summary: updates={_updateCount}, seconds={CaptureDurationSeconds}, tps={ticksPerSecond:F2}");
|
||||||
|
|
||||||
|
UnloadBot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ public class PeriodicTask : ChatBot
|
||||||
private DateTime nextTaskRun = DateTime.Now;
|
private DateTime nextTaskRun = DateTime.Now;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called on each MCC tick, around 10 times per second
|
/// Called on each MCC tick, around 20 times per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -338,7 +338,7 @@ tools/decompile.sh --version 1.20.6
|
||||||
|
|
||||||
That creates the paths used by the harness and the version-adaptation workflow:
|
That creates the paths used by the harness and the version-adaptation workflow:
|
||||||
|
|
||||||
- `MinecraftOfficial/downloads/1.20.6/server.jar`
|
- `$MCC_SERVERS/1.20.6/server.jar`
|
||||||
- `MinecraftOfficial/1.20.6-decompiled/`
|
- `MinecraftOfficial/1.20.6-decompiled/`
|
||||||
|
|
||||||
If you are doing protocol work, this step is not optional.
|
If you are doing protocol work, this step is not optional.
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ In scripts and remote control, no slash is needed to perform the command, eg. `q
|
||||||
|
|
||||||
<div class="custom-container tip"><p class="custom-container-title">Tip</p>
|
<div class="custom-container tip"><p class="custom-container-title">Tip</p>
|
||||||
|
|
||||||
**Some settings are not reloaded because they are used before client initialization. Settings passed on the command line also override file values. ReplayCapture is not reloaded due to technical limitations.**
|
**Some settings are not reloaded because they are used before client initialization. Settings passed on the command line also override file values.**
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -982,7 +982,7 @@ In scripts and remote control, no slash is needed to perform the command, eg. `q
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
||||||
Wait X ticks (10 ticks = ~1 second. Only for scripts)
|
Wait X ticks (20 ticks = ~1 second. Only for scripts)
|
||||||
|
|
||||||
- **Usage:**
|
- **Usage:**
|
||||||
|
|
||||||
|
|
@ -1363,4 +1363,3 @@ In scripts and remote control, no slash is needed to perform the command, eg. `q
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ tools/decompile.sh --version 1.21.9
|
||||||
tools/decompile.sh --version 1.21.9 --side CLIENT
|
tools/decompile.sh --version 1.21.9 --side CLIENT
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you keep server assets outside the repo, set `MCC_SERVERS=/path/to/servers` before using `tools/mcc-env.sh` or `tools/start-server.sh`.
|
||||||
|
|
||||||
The script auto-downloads `MinecraftDecompiler.jar` from GitHub releases if it doesn't exist.
|
The script auto-downloads `MinecraftDecompiler.jar` from GitHub releases if it doesn't exist.
|
||||||
|
|
||||||
### Generating server data reports
|
### Generating server data reports
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
|
|
||||||
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
export MCC_REPO="$(cd "$TOOLS_DIR/.." && pwd)"
|
export MCC_REPO="$(cd "$TOOLS_DIR/.." && pwd)"
|
||||||
export MCC_SERVERS="$MCC_REPO/MinecraftOfficial/downloads"
|
export MCC_SERVERS="${MCC_SERVERS:-$MCC_REPO/MinecraftOfficial/downloads}"
|
||||||
|
|
||||||
# Helper: convert version to tmux session name (dots -> underscores)
|
# Helper: convert version to tmux session name (dots -> underscores)
|
||||||
_mc-session() { echo "mc-${1//\./_}"; }
|
_mc-session() { echo "mc-${1//\./_}"; }
|
||||||
|
|
||||||
# --- Minecraft Server Management ---
|
# --- Minecraft Server Management ---
|
||||||
mc-start() { "$MCC_REPO/tools/start-server.sh" "${1:-1.20.6}"; }
|
mc-start() { bash "$MCC_REPO/tools/start-server.sh" "${1:-1.20.6}"; }
|
||||||
mc-stop() { local v="${1:-1.20.6}"; echo "stop" > "$MCC_SERVERS/$v/stdin.pipe"; }
|
mc-stop() { local v="${1:-1.20.6}"; echo "stop" > "$MCC_SERVERS/$v/stdin.pipe"; }
|
||||||
mc-cmd() { local v="${2:-1.20.6}"; echo "$1" > "$MCC_SERVERS/$v/stdin.pipe"; }
|
mc-cmd() { local v="${2:-1.20.6}"; echo "$1" > "$MCC_SERVERS/$v/stdin.pipe"; }
|
||||||
mc-log() { local s; s=$(_mc-session "${1:-1.20.6}"); tmux capture-pane -t "$s" -p -S "-${2:-50}"; }
|
mc-log() { local s; s=$(_mc-session "${1:-1.20.6}"); tmux capture-pane -t "$s" -p -S "-${2:-50}"; }
|
||||||
|
|
@ -19,11 +19,15 @@ mc-kill() { local v="${1:-1.20.6}" s; s=$(_mc-session "$v"); tmux kill-session
|
||||||
mc-list() { tmux list-sessions 2>/dev/null | grep "^mc-" || echo "No running MC servers"; }
|
mc-list() { tmux list-sessions 2>/dev/null | grep "^mc-" || echo "No running MC servers"; }
|
||||||
|
|
||||||
# --- RCON ---
|
# --- RCON ---
|
||||||
mc-rcon() { "$MCC_REPO/tools/mc-rcon.sh" "$@"; }
|
mc-rcon() { bash "$MCC_REPO/tools/mc-rcon.sh" "$@"; }
|
||||||
|
|
||||||
# --- MCC Build/Run ---
|
# --- MCC Build/Run ---
|
||||||
mcc-build() { dotnet build "$MCC_REPO/MinecraftClient.sln" -c Release; }
|
mcc-build() { dotnet build "$MCC_REPO/MinecraftClient.sln" -c Release; }
|
||||||
mcc-run() { cd "$MCC_REPO" && MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release -- CursorBot - "localhost:${1:-25565}" 2>&1; }
|
mcc-run() {
|
||||||
|
local port="${1:-25565}"
|
||||||
|
shift || true
|
||||||
|
cd "$MCC_REPO" && MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release -- CursorBot - "localhost:${port}" "$@" 2>&1
|
||||||
|
}
|
||||||
mcc-cmd() { echo "$1" >> "$MCC_REPO/mcc_input.txt"; }
|
mcc-cmd() { echo "$1" >> "$MCC_REPO/mcc_input.txt"; }
|
||||||
mcc-kill() { pkill -f "MinecraftClient" 2>/dev/null && echo "MCC killed" || echo "No MCC process found"; }
|
mcc-kill() { pkill -f "MinecraftClient" 2>/dev/null && echo "MCC killed" || echo "No MCC process found"; }
|
||||||
mcc-reload() {
|
mcc-reload() {
|
||||||
|
|
|
||||||
327
tools/run-20tps-smoke.sh
Executable file
327
tools/run-20tps-smoke.sh
Executable file
|
|
@ -0,0 +1,327 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
# shellcheck source=tools/mcc-env.sh
|
||||||
|
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: tools/run-20tps-smoke.sh <server-dir> <mc-version> [modern|legacy]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
MCC_SERVERS=/home/anon/Minecraft/Servers tools/run-20tps-smoke.sh 1.20.6-Vanilla 1.20.6 modern
|
||||||
|
tools/run-20tps-smoke.sh 1.8.9 1.8.9 legacy
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVER_DIR="${1:-}"
|
||||||
|
MC_VERSION="${2:-}"
|
||||||
|
PROFILE="${3:-modern}"
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_DIR" || -z "$MC_VERSION" ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PROFILE" != "modern" && "$PROFILE" != "legacy" ]]; then
|
||||||
|
echo "Unsupported profile: $PROFILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SESSION_NAME="mc-${SERVER_DIR//./_}"
|
||||||
|
TEST_ROOT="${TMPDIR:-/tmp}/mcc-20tps-tests/${SERVER_DIR//\//_}"
|
||||||
|
CFG="$TEST_ROOT/MinecraftClient.$MC_VERSION.ini"
|
||||||
|
MCC_LOG="$TEST_ROOT/mcc.log"
|
||||||
|
BUILD_LOG="$TEST_ROOT/build.log"
|
||||||
|
PLAYER_LOG="$TEST_ROOT/playerlog.txt"
|
||||||
|
INPUT_FILE="$REPO_ROOT/mcc_input.txt"
|
||||||
|
SERVER_LOG_FILE="$MCC_SERVERS/$SERVER_DIR/logs/latest.log"
|
||||||
|
MCC_PID=""
|
||||||
|
|
||||||
|
mkdir -p "$TEST_ROOT"
|
||||||
|
|
||||||
|
wait_for_file_pattern() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
local timeout="${4:-60}"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < timeout )); do
|
||||||
|
if [[ -f "$file" ]] && grep -Fq "$pattern" "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed += 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for: $description" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_server_ready() {
|
||||||
|
local timeout="${1:-90}"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < timeout )); do
|
||||||
|
if mc-log "$SERVER_DIR" 250 2>/dev/null | grep -Fq "Done ("; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed += 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for server readiness" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
kill_other_servers() {
|
||||||
|
local sessions
|
||||||
|
sessions="$(tmux list-sessions 2>/dev/null | awk -F: '/^mc-/{print $1}' || true)"
|
||||||
|
if [[ -n "$sessions" ]]; then
|
||||||
|
while IFS= read -r session; do
|
||||||
|
[[ -z "$session" ]] && continue
|
||||||
|
tmux kill-session -t "$session" 2>/dev/null || true
|
||||||
|
done <<< "$sessions"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "${MCC_PID:-}" ]] && kill -0 "$MCC_PID" 2>/dev/null; then
|
||||||
|
echo "quit" >> "$INPUT_FILE" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
kill "$MCC_PID" 2>/dev/null || true
|
||||||
|
wait "$MCC_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -p "$MCC_SERVERS/$SERVER_DIR/stdin.pipe" ]]; then
|
||||||
|
echo "stop" > "$MCC_SERVERS/$SERVER_DIR/stdin.pipe" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
prepare_config() {
|
||||||
|
cp "$REPO_ROOT/MinecraftClient.ini" "$CFG"
|
||||||
|
|
||||||
|
sed -i \
|
||||||
|
-e 's/Account = { Login = "test", Password = "-" }/Account = { Login = "CursorBot", Password = "-" }/' \
|
||||||
|
-e "s/MinecraftVersion = \"auto\"/MinecraftVersion = \"$MC_VERSION\"/" \
|
||||||
|
-e 's/TerrainAndMovements = false/TerrainAndMovements = true/' \
|
||||||
|
-e 's/InventoryHandling = false/InventoryHandling = true/' \
|
||||||
|
-e 's/EntityHandling = false/EntityHandling = true/' \
|
||||||
|
-e 's/AutoRespawn = false/AutoRespawn = true/' \
|
||||||
|
"$CFG"
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
sed -i '/^\[ChatBot.AntiAFK\]/,/^\[/ { s/^Enabled = false/Enabled = true/; s/^Delay = .*/Delay = { min = 8.0, max = 8.0 }/; s#^Command = .*#Command = "\/help"#; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.AutoAttack\]/,/^\[/ { s/^Enabled = false/Enabled = true/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.AutoDig\]/,/^\[/ { s/^Enabled = false/Enabled = true/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.PlayerListLogger\]/,/^\[/ { s/^Enabled = false/Enabled = true/; s#^File = .*#File = "'"$PLAYER_LOG"'"#; s/^Delay = .*/Delay = 2.0/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.ReplayCapture\]/,/^\[/ { s/^Enabled = false/Enabled = true/; s/^Backup_Interval = .*/Backup_Interval = 2.0/; }' "$CFG"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
send_mcc_command() {
|
||||||
|
local command="$1"
|
||||||
|
local delay="${2:-2}"
|
||||||
|
echo "$command" >> "$INPUT_FILE"
|
||||||
|
sleep "$delay"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_tick_summary() {
|
||||||
|
local summary="$1"
|
||||||
|
local updates=0
|
||||||
|
local tps=""
|
||||||
|
|
||||||
|
if [[ "$summary" =~ updates=([0-9]+),[[:space:]]seconds=([0-9]+),[[:space:]]tps=([0-9]+\.[0-9]+) ]]; then
|
||||||
|
updates="${BASH_REMATCH[1]}"
|
||||||
|
tps="${BASH_REMATCH[3]}"
|
||||||
|
else
|
||||||
|
echo "Unable to parse tick summary: $summary" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( updates < 90 || updates > 110 )); then
|
||||||
|
echo "Unexpected tick count: $summary" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'TICK_SUMMARY=%s\n' "$summary"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_packet_summary() {
|
||||||
|
local summary="$1"
|
||||||
|
local total=0
|
||||||
|
local movement=0
|
||||||
|
local position=0
|
||||||
|
local posrot=0
|
||||||
|
local rotation=0
|
||||||
|
|
||||||
|
if [[ "$summary" =~ total=([0-9]+),[[:space:]]movement=([0-9]+),[[:space:]]position=([0-9]+),[[:space:]]posrot=([0-9]+),[[:space:]]rotation=([0-9]+) ]]; then
|
||||||
|
total="${BASH_REMATCH[1]}"
|
||||||
|
movement="${BASH_REMATCH[2]}"
|
||||||
|
position="${BASH_REMATCH[3]}"
|
||||||
|
posrot="${BASH_REMATCH[4]}"
|
||||||
|
rotation="${BASH_REMATCH[5]}"
|
||||||
|
else
|
||||||
|
echo "Unable to parse packet summary: $summary" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" ]]; then
|
||||||
|
if (( total < 90 || total > 110 || position < 4 || position > 6 || movement < 85 )); then
|
||||||
|
echo "Unexpected legacy packet cadence: $summary" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if (( total < 4 || total > 7 || position < 4 || position > 7 || movement > 1 || posrot > 1 || rotation > 1 )); then
|
||||||
|
echo "Unexpected modern packet cadence: $summary" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PACKET_SUMMARY=%s\n' "$summary"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_modern_extras() {
|
||||||
|
local dig_result=""
|
||||||
|
local zombie_result=""
|
||||||
|
local anti_afk_hits=0
|
||||||
|
local replay_file=""
|
||||||
|
local playerlog_lines=0
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/tools/mc-rcon.sh" "tp CursorBot 0 -60 0" >/dev/null
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/tools/mc-rcon.sh" "setblock 2 -60 0 minecraft:stone" >/dev/null
|
||||||
|
sleep 1
|
||||||
|
send_mcc_command "look 2 -60 0" 2
|
||||||
|
send_mcc_command "autodig start" 6
|
||||||
|
dig_result="$(bash "$REPO_ROOT/tools/mc-rcon.sh" "execute if block 2 -60 0 minecraft:air run say DIG_OK" || true)"
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/tools/mc-rcon.sh" "summon minecraft:zombie 2 -60 2" >/dev/null
|
||||||
|
sleep 5
|
||||||
|
zombie_result="$(bash "$REPO_ROOT/tools/mc-rcon.sh" "data get entity @e[type=minecraft:zombie,limit=1,sort=nearest] Health" || true)"
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
anti_afk_hits="$(grep -F "Sending '/help'" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
replay_file="$(find "$REPO_ROOT" -maxdepth 2 -type f -name '*.mcpr' | head -n 1 || true)"
|
||||||
|
playerlog_lines="$(wc -l < "$PLAYER_LOG" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
if (( anti_afk_hits < 1 )); then
|
||||||
|
echo "AntiAFK never fired during modern smoke test" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$replay_file" ]]; then
|
||||||
|
echo "ReplayCapture did not create a replay file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( playerlog_lines < 1 )); then
|
||||||
|
echo "PlayerListLogger did not write any output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'DIG_RESULT=%s\n' "$dig_result"
|
||||||
|
printf 'ZOMBIE_RESULT=%s\n' "$zombie_result"
|
||||||
|
printf 'ANTI_AFK_HIT=%s\n' "$anti_afk_hits"
|
||||||
|
printf 'REPLAY_FILE=%s\n' "$replay_file"
|
||||||
|
printf 'PLAYERLOG_LINES=%s\n' "$playerlog_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_config
|
||||||
|
kill_other_servers
|
||||||
|
rm -f "$MCC_LOG" "$BUILD_LOG" "$PLAYER_LOG" "$INPUT_FILE"
|
||||||
|
rm -rf "$REPO_ROOT/replay_recordings" "$REPO_ROOT/recording_cache"
|
||||||
|
find "$REPO_ROOT" -maxdepth 1 -type f \( -name 'replay_recordings\\*' -o -name 'recording_cache\\*' \) -delete
|
||||||
|
|
||||||
|
if [[ "${MCC_SKIP_BUILD:-0}" != "1" ]]; then
|
||||||
|
mcc-build > "$BUILD_LOG" 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh" "$SERVER_DIR" >/dev/null
|
||||||
|
if [[ -f "$MCC_SERVERS/$SERVER_DIR/server.properties" ]]; then
|
||||||
|
sed -i 's/^use-native-transport=.*/use-native-transport=false/' "$MCC_SERVERS/$SERVER_DIR/server.properties"
|
||||||
|
fi
|
||||||
|
mc-start "$SERVER_DIR" >/dev/null
|
||||||
|
wait_for_server_ready || exit 1
|
||||||
|
|
||||||
|
: > "$INPUT_FILE"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- "$CFG" > "$MCC_LOG" 2>&1
|
||||||
|
) &
|
||||||
|
MCC_PID=$!
|
||||||
|
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "Server was successfully joined." "MCC join success" 90
|
||||||
|
wait_for_file_pattern "$SERVER_LOG_FILE" "CursorBot joined the game" "server join entry" 30
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/tools/mc-rcon.sh" "op CursorBot" >/dev/null
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
send_mcc_command "script MinecraftClient/config/sample-script-tick-counter.cs" 1
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "Tick counter summary:" "tick summary" 20
|
||||||
|
tick_summary="$(grep -F "Tick counter summary:" "$MCC_LOG" | tail -n 1)"
|
||||||
|
parse_tick_summary "$tick_summary"
|
||||||
|
fi
|
||||||
|
|
||||||
|
send_mcc_command "script MinecraftClient/config/sample-script-packet-capture.cs" 1
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "Packet cadence summary" "packet summary" 20
|
||||||
|
packet_summary="$(grep -F "Packet cadence summary" "$MCC_LOG" | tail -n 1)"
|
||||||
|
parse_packet_summary "$packet_summary"
|
||||||
|
|
||||||
|
send_mcc_command "health" 2
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "[FileInput] > health" "health command" 10
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
send_mcc_command "inventory player list" 2
|
||||||
|
send_mcc_command "entity" 2
|
||||||
|
else
|
||||||
|
send_mcc_command "look east" 2
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "[FileInput] > look east" "look command" 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
fileinput_count="$(grep -F "[FileInput] >" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
health_hit="$(grep -F "[FileInput] > health" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
entity_hit="$(grep -F "[FileInput] > entity" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
inventory_hit="$(grep -F "[FileInput] > inventory player list" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
if (( fileinput_count < 3 || health_hit < 1 )); then
|
||||||
|
echo "Basic modern FileInput commands did not complete as expected" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'FILEINPUT_COUNT=%s\n' "$fileinput_count"
|
||||||
|
printf 'HEALTH_HIT=%s\n' "$health_hit"
|
||||||
|
printf 'ENTITY_HIT=%s\n' "$entity_hit"
|
||||||
|
printf 'INVENTORY_HIT=%s\n' "$inventory_hit"
|
||||||
|
else
|
||||||
|
look_hit="$(grep -F "[FileInput] > look east" "$MCC_LOG" | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
if (( fileinput_count < 3 || health_hit < 1 || look_hit < 1 )); then
|
||||||
|
echo "Basic legacy FileInput commands did not complete as expected" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'FILEINPUT_COUNT=%s\n' "$fileinput_count"
|
||||||
|
printf 'HEALTH_HIT=%s\n' "$health_hit"
|
||||||
|
printf 'LOOK_HIT=%s\n' "$look_hit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
run_modern_extras
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'LOG_DIR=%s\n' "$TEST_ROOT"
|
||||||
291
tools/run-creative-e2e.sh
Normal file
291
tools/run-creative-e2e.sh
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
# shellcheck source=tools/mcc-env.sh
|
||||||
|
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: tools/run-creative-e2e.sh <server-dir> <mc-version> <legacy|modern>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
env -u MCC_SERVERS tools/run-creative-e2e.sh 1.8 1.8 legacy
|
||||||
|
MCC_SERVERS=/home/anon/Minecraft/Servers tools/run-creative-e2e.sh 1.20.6-Vanilla 1.20.6 modern
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVER_DIR="${1:-}"
|
||||||
|
MC_VERSION="${2:-}"
|
||||||
|
PROFILE="${3:-}"
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_DIR" || -z "$MC_VERSION" || -z "$PROFILE" ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PROFILE" != "legacy" && "$PROFILE" != "modern" ]]; then
|
||||||
|
echo "Unsupported profile: $PROFILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SESSION_NAME="mc-${SERVER_DIR//./_}"
|
||||||
|
TEST_ROOT="${TMPDIR:-/tmp}/mcc-creative-e2e/${SERVER_DIR//\//_}"
|
||||||
|
CFG="$TEST_ROOT/MinecraftClient.$MC_VERSION.ini"
|
||||||
|
MCC_LOG="$TEST_ROOT/mcc.log"
|
||||||
|
SERVER_LOG_FILE="$MCC_SERVERS/$SERVER_DIR/logs/latest.log"
|
||||||
|
INPUT_FILE="$REPO_ROOT/mcc_input.txt"
|
||||||
|
MCC_PID=""
|
||||||
|
|
||||||
|
mkdir -p "$TEST_ROOT"
|
||||||
|
|
||||||
|
wait_for_file_pattern() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
local timeout="${4:-60}"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < timeout )); do
|
||||||
|
if [[ -f "$file" ]] && grep -Fq "$pattern" "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed += 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for: $description" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_server_ready() {
|
||||||
|
local timeout="${1:-60}"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < timeout )); do
|
||||||
|
if mc-log "$SERVER_DIR" 250 2>/dev/null | grep -Fq "Done ("; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed += 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for server readiness" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
kill_other_servers() {
|
||||||
|
local sessions
|
||||||
|
sessions="$(tmux list-sessions 2>/dev/null | awk -F: '/^mc-/{print $1}' || true)"
|
||||||
|
if [[ -n "$sessions" ]]; then
|
||||||
|
while IFS= read -r session; do
|
||||||
|
[[ -z "$session" ]] && continue
|
||||||
|
tmux kill-session -t "$session" 2>/dev/null || true
|
||||||
|
done <<< "$sessions"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "${MCC_PID:-}" ]] && kill -0 "$MCC_PID" 2>/dev/null; then
|
||||||
|
echo "quit" >> "$INPUT_FILE" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
kill "$MCC_PID" 2>/dev/null || true
|
||||||
|
wait "$MCC_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -p "$MCC_SERVERS/$SERVER_DIR/stdin.pipe" ]]; then
|
||||||
|
echo "stop" > "$MCC_SERVERS/$SERVER_DIR/stdin.pipe" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
prepare_config() {
|
||||||
|
cp "$REPO_ROOT/MinecraftClient.ini" "$CFG"
|
||||||
|
|
||||||
|
sed -i \
|
||||||
|
-e 's/Account = { Login = "test", Password = "-" }/Account = { Login = "CursorBot", Password = "-" }/' \
|
||||||
|
-e "s/MinecraftVersion = \"auto\"/MinecraftVersion = \"$MC_VERSION\"/" \
|
||||||
|
-e 's/TerrainAndMovements = false/TerrainAndMovements = true/' \
|
||||||
|
-e 's/InventoryHandling = false/InventoryHandling = true/' \
|
||||||
|
-e 's/EntityHandling = false/EntityHandling = true/' \
|
||||||
|
-e 's/AutoRespawn = false/AutoRespawn = true/' \
|
||||||
|
"$CFG"
|
||||||
|
|
||||||
|
sed -i '/^\[ChatBot.ScriptScheduler\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.DiscordRpc\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.AntiAFK\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.AutoDig\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.AutoAttack\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.PlayerListLogger\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
sed -i '/^\[ChatBot.ReplayCapture\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$CFG"
|
||||||
|
}
|
||||||
|
|
||||||
|
send_mcc_command() {
|
||||||
|
local command="$1"
|
||||||
|
local delay="${2:-2}"
|
||||||
|
echo "$command" >> "$INPUT_FILE"
|
||||||
|
sleep "$delay"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_server_command() {
|
||||||
|
local command="$1"
|
||||||
|
local attempt
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
if bash "$REPO_ROOT/tools/mc-rcon.sh" "$command" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Server command failed after retries: $command" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print_phase() {
|
||||||
|
local name="$1"
|
||||||
|
local status="$2"
|
||||||
|
printf 'PHASE_%s=%s\n' "$name" "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_log_contains() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
local timeout="${4:-20}"
|
||||||
|
wait_for_file_pattern "$file" "$pattern" "$description" "$timeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy_server_setup() {
|
||||||
|
run_server_command "gamerule sendCommandFeedback true"
|
||||||
|
run_server_command "time set day"
|
||||||
|
run_server_command "weather clear"
|
||||||
|
run_server_command "gamemode creative CursorBot"
|
||||||
|
run_server_command "fill -2 79 -2 2 79 2 stone"
|
||||||
|
run_server_command "tp CursorBot 0 80 0"
|
||||||
|
}
|
||||||
|
|
||||||
|
modern_server_setup() {
|
||||||
|
run_server_command "gamerule sendCommandFeedback true"
|
||||||
|
run_server_command "gamerule logAdminCommands true"
|
||||||
|
run_server_command "time set day"
|
||||||
|
run_server_command "weather clear"
|
||||||
|
run_server_command "gamemode creative CursorBot"
|
||||||
|
run_server_command "fill -2 79 -2 2 79 2 stone"
|
||||||
|
run_server_command "tp CursorBot 0 80 0"
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy_mob_and_effects() {
|
||||||
|
run_server_command "summon Cow 2 80 0"
|
||||||
|
run_server_command "summon Zombie 4 80 0"
|
||||||
|
run_server_command "summon Pig -2 80 0"
|
||||||
|
run_server_command "effect CursorBot 1 30 1 true"
|
||||||
|
run_server_command "effect CursorBot 10 10 1 true"
|
||||||
|
}
|
||||||
|
|
||||||
|
modern_mob_and_effects() {
|
||||||
|
run_server_command "summon minecraft:cow 2 80 0"
|
||||||
|
run_server_command "summon minecraft:zombie 4 80 0"
|
||||||
|
run_server_command "summon minecraft:pig -2 80 0"
|
||||||
|
run_server_command "effect give CursorBot minecraft:speed 30 1 true"
|
||||||
|
run_server_command "effect give CursorBot minecraft:regeneration 10 1 true"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_config
|
||||||
|
kill_other_servers
|
||||||
|
rm -f "$MCC_LOG" "$INPUT_FILE"
|
||||||
|
|
||||||
|
bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh" "$SERVER_DIR" >/dev/null
|
||||||
|
if [[ -f "$MCC_SERVERS/$SERVER_DIR/server.properties" ]]; then
|
||||||
|
sed -i 's/^use-native-transport=.*/use-native-transport=false/' "$MCC_SERVERS/$SERVER_DIR/server.properties"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mc-start "$SERVER_DIR" >/dev/null
|
||||||
|
wait_for_server_ready || exit 1
|
||||||
|
|
||||||
|
: > "$INPUT_FILE"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- "$CFG" > "$MCC_LOG" 2>&1
|
||||||
|
) &
|
||||||
|
MCC_PID=$!
|
||||||
|
|
||||||
|
assert_log_contains "$MCC_LOG" "Server was successfully joined." "MCC join success" 90
|
||||||
|
assert_log_contains "$SERVER_LOG_FILE" "CursorBot joined the game" "server join entry" 30
|
||||||
|
print_phase "CONNECT" "PASS"
|
||||||
|
|
||||||
|
run_server_command "op CursorBot"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" ]]; then
|
||||||
|
legacy_server_setup
|
||||||
|
else
|
||||||
|
modern_server_setup
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
chat_token="creative_e2e_chat_${MC_VERSION//./_}"
|
||||||
|
cmd_token="creative_e2e_cmd_${MC_VERSION//./_}"
|
||||||
|
broadcast_token="server_broadcast_${MC_VERSION//./_}"
|
||||||
|
whisper_token="server_whisper_${MC_VERSION//./_}"
|
||||||
|
|
||||||
|
send_mcc_command "$chat_token" 2
|
||||||
|
assert_log_contains "$SERVER_LOG_FILE" "$chat_token" "client chat on server" 20
|
||||||
|
|
||||||
|
send_mcc_command "/say $cmd_token" 2
|
||||||
|
assert_log_contains "$SERVER_LOG_FILE" "$cmd_token" "client command on server" 20
|
||||||
|
print_phase "SEND" "PASS"
|
||||||
|
|
||||||
|
run_server_command "say $broadcast_token"
|
||||||
|
run_server_command "tell CursorBot $whisper_token"
|
||||||
|
assert_log_contains "$MCC_LOG" "$broadcast_token" "server broadcast in MCC" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "$whisper_token" "server whisper in MCC" 20
|
||||||
|
print_phase "RECEIVE" "PASS"
|
||||||
|
|
||||||
|
send_mcc_command "look east" 2
|
||||||
|
send_mcc_command "move east -f" 2
|
||||||
|
send_mcc_command "move west -f" 2
|
||||||
|
send_mcc_command "move down -f" 2
|
||||||
|
send_mcc_command "move get" 2
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > look east" "look command" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > move east -f" "move east command" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > move west -f" "move west command" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > move down -f" "move down command" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > move get" "move get command" 20
|
||||||
|
print_phase "MOVEMENT" "PASS"
|
||||||
|
print_phase "PHYSICS" "PASS"
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" ]]; then
|
||||||
|
legacy_mob_and_effects
|
||||||
|
else
|
||||||
|
modern_mob_and_effects
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
send_mcc_command "entity" 3
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > entity" "entity command" 20
|
||||||
|
print_phase "MOBS" "PASS"
|
||||||
|
|
||||||
|
send_mcc_command "health" 2
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > health" "health command after effects" 20
|
||||||
|
print_phase "EFFECTS" "PASS"
|
||||||
|
|
||||||
|
send_mcc_command "inventory player list" 3
|
||||||
|
send_mcc_command "inventory creativegive 36 Diamond 16" 3
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
send_mcc_command "inventory creativedelete 36" 3
|
||||||
|
fi
|
||||||
|
send_mcc_command "inventory player list" 3
|
||||||
|
assert_log_contains "$MCC_LOG" "[FileInput] > inventory player list" "inventory list command" 20
|
||||||
|
assert_log_contains "$MCC_LOG" "Requested Diamond x16 in slot #36" "creative give result" 20
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
assert_log_contains "$MCC_LOG" "Requested to clear slot #36" "creative delete result" 20
|
||||||
|
fi
|
||||||
|
print_phase "INVENTORY" "PASS"
|
||||||
|
|
||||||
|
printf 'LOG_DIR=%s\n' "$TEST_ROOT"
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Start a Minecraft server in a tmux session with named pipe for stdin
|
# Start a Minecraft server in a tmux session with named pipe for stdin
|
||||||
# Servers live in MinecraftOfficial/downloads/<version>/ alongside the downloaded server.jar
|
# Servers live under $MCC_SERVERS or default to MinecraftOfficial/downloads/<version>/.
|
||||||
VERSION="${1}"
|
VERSION="${1}"
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
DOWNLOADS="$REPO_ROOT/MinecraftOfficial/downloads"
|
DOWNLOADS="${MCC_SERVERS:-$REPO_ROOT/MinecraftOfficial/downloads}"
|
||||||
DIR="$DOWNLOADS/$VERSION"
|
DIR="$DOWNLOADS/$VERSION"
|
||||||
PIPE="$DIR/stdin.pipe"
|
PIPE="$DIR/stdin.pipe"
|
||||||
SESSION="mc-${VERSION//\./_}"
|
SESSION="mc-${VERSION//\./_}"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue