Implement support for TwoWayPing CPE extension, add a /ping command to measure player ping.

This commit is contained in:
UnknownShadow200 2017-06-22 00:15:13 +10:00
parent 3c26d97c07
commit 35b1f186ba
11 changed files with 199 additions and 6 deletions

View File

@ -0,0 +1,58 @@
/*
Copyright 2015 MCGalaxy
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using MCGalaxy.Network;
namespace MCGalaxy.Commands.Chatting {
public sealed class CmdPing : Command {
public override string name { get { return "ping"; } }
public override string type { get { return CommandTypes.Information; } }
public override bool museumUsable { get { return true; } }
public override LevelPermission defaultRank { get { return LevelPermission.Guest; } }
public override CommandPerm[] ExtraPerms {
get { return new[] { new CommandPerm(LevelPermission.Admin, "+ can see ping of all players") }; }
}
public override void Use(Player p, string message) {
if (!message.CaselessEq("all")) {
if (Player.IsSuper(p)) { Player.Message(p, "Super users cannot measure their own ping."); return; }
if (p.Ping.AveragePingMilliseconds() == 0) {
Player.Message(p, "Your client does not support measuring ping.");
} else {
Player.Message(p, p.Ping.Format());
}
} else {
if (!CheckExtraPerm(p)) { MessageNeedExtra(p); return; }
Player[] players = PlayerInfo.Online.Items;
Player.Message(p, "Ping/latency list for online players:");
foreach (Player pl in players) {
if (!Entities.CanSee(p, pl)) continue;
if (pl.Ping.AveragePingMilliseconds() == 0) continue;
Player.Message(p, pl.ColoredName + " %S- " + pl.Ping.Format());
}
}
}
public override void Help(Player p) {
Player.Message(p, "%T/ping %H- Outputs details about your ping to the server.");
Player.Message(p, "%T/ping all %H- Outputs ping details for all players.");
Player.Message(p, "&cNOTE: %HNot all clients support measuring ping.");
}
}
}

View File

@ -199,6 +199,7 @@
<Compile Include="Commands\CPE\CmdEntityRot.cs" />
<Compile Include="Commands\CPE\CmdEnvironment.cs" />
<Compile Include="Commands\CPE\CmdModel.cs" />
<Compile Include="Commands\CPE\CmdPing.cs" />
<Compile Include="Commands\CPE\CmdReachDistance.cs" />
<Compile Include="Commands\CPE\CmdSkin.cs" />
<Compile Include="Commands\CPE\CmdTexture.cs" />
@ -576,6 +577,7 @@
<Compile Include="Network\Utils\HttpUtil.cs" />
<Compile Include="Network\Utils\LevelChunkStream.cs" />
<Compile Include="Network\Utils\NetUtils.cs" />
<Compile Include="Network\Utils\PingList.cs" />
<Compile Include="Player\Group\Group.cs" />
<Compile Include="Player\Group\GroupProperties.cs" />
<Compile Include="Player\List\PlayerExtList.cs" />

View File

@ -66,5 +66,6 @@ namespace MCGalaxy.Network {
public const byte CpeSetMapEnvUrl = 40;
public const byte CpeSetMapEnvProperty = 41;
public const byte CpeSetEntityProperty = 42;
public const byte CpeTwoWayPing = 43;
}
}

View File

@ -247,5 +247,13 @@ namespace MCGalaxy.Network {
NetUtils.WriteI32(value, buffer, 3);
return buffer;
}
public static byte[] TwoWayPing(bool serverToClient, ushort data) {
byte[] buffer = new byte[4];
buffer[0] = Opcode.CpeTwoWayPing;
buffer[1] = (byte)(serverToClient ? 1 : 0);
NetUtils.WriteU16(data, buffer, 2);
return buffer;
}
}
}

View File

@ -59,6 +59,19 @@ namespace MCGalaxy {
OnPlayerClickEvent.Call(this, Button, Action, Yaw, Pitch, EntityID, X, Y, Z, face);
}
void HandleTwoWayPing(byte[] packet) {
bool serverToClient = packet[1] != 0;
ushort data = NetUtils.ReadU16(packet, 2);
if (!serverToClient) {
// Client-> server ping, immediately send reply.
Send(Packet.TwoWayPing(false, data));
} else {
// Server -> client ping, set time received for reply.
Ping.Update(data);
}
}
void CheckReadAllExtensions() {
if (extensionCount <= 0 && !finishedCpeLogin) {
CompleteLoginProcess();
@ -99,7 +112,7 @@ namespace MCGalaxy {
public static void SendMessage(Player p, string message, bool colorParse) {
if (p == null) {
Logger.Log(LogType.ConsoleMessage, message);
Logger.Log(LogType.ConsoleMessage, message);
} else {
p.SendMessage(0, message, colorParse);
}

View File

@ -0,0 +1,96 @@
/*
Copyright 2015 MCGalaxy
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
namespace MCGalaxy.Network {
public sealed class PingList {
public struct PingEntry {
public DateTime TimeSent, TimeReceived;
public ushort Data;
}
public PingEntry[] Entries = new PingEntry[10];
public ushort NextTwoWayPingData() {
// Find free ping slot
for (int i = 0; i < Entries.Length; i++) {
if (Entries[i].TimeSent.Ticks != 0) continue;
ushort prev = i > 0 ? Entries[i - 1].Data : (ushort)0;
return SetTwoWayPing(i, prev);
}
// Remove oldest ping slot
for (int i = 0; i < Entries.Length - 1; i++) {
Entries[i] = Entries[i + 1];
}
int j = Entries.Length - 1;
return SetTwoWayPing(j, Entries[j].Data);
}
ushort SetTwoWayPing(int i, ushort prev) {
Entries[i].Data = (ushort)(prev + 1);
Entries[i].TimeSent = DateTime.UtcNow;
return (ushort)(prev + 1);
}
public void Update(ushort data) {
for (int i = 0; i < Entries.Length; i++ ) {
if (Entries[i].Data != data) continue;
Entries[i].TimeReceived = DateTime.UtcNow;
return;
}
}
/// <summary> Gets average ping in milliseconds, or 0 if no ping measures. </summary>
public double AveragePingMilliseconds() {
double totalMs = 0;
int measures = 0;
foreach (PingEntry ping in Entries) {
if (ping.TimeSent.Ticks == 0 || ping.TimeReceived.Ticks == 0) continue;
totalMs += (ping.TimeReceived - ping.TimeSent).TotalMilliseconds;
measures++;
}
return measures == 0 ? 0 : (totalMs / measures);
}
/// <summary> Gets worst ping in milliseconds, or 0 if no ping measures. </summary>
public double WorstPingMilliseconds() {
double totalMs = 0;
foreach (PingEntry ping in Entries) {
if (ping.TimeSent.Ticks == 0 || ping.TimeReceived.Ticks == 0) continue;
double ms = (ping.TimeReceived - ping.TimeSent).TotalMilliseconds;
totalMs = Math.Max(totalMs, ms);
}
return totalMs;
}
public string Format() {
return String.Format(" Worst ping {0}ms, average {1}ms",
WorstPingMilliseconds().ToString("N0"),
AveragePingMilliseconds().ToString("N0"));
}
}
}

View File

@ -26,11 +26,11 @@ namespace MCGalaxy {
public int ChangeModel, EnvMapAppearance, EnvWeatherType, HackControl;
public int EmoteFix, MessageTypes, LongerMessages, FullCP437;
public int BlockDefinitions, BlockDefinitionsExt, TextColors, BulkBlockUpdate;
public int EnvMapAspect, PlayerClick, EntityProperty, ExtEntityPositions;
public int EnvMapAspect, PlayerClick, EntityProperty, ExtEntityPositions, TwoWayPing;
// these are checked frequently, so avoid overhead of HasCpeExt
// these are checked very frequently, so avoid overhead of HasCpeExt
public bool hasCustomBlocks, hasBlockDefs,
hasTextColors, hasChangeModel, hasExtList, hasCP437;
hasTextColors, hasChangeModel, hasExtList, hasCP437, hasTwoWayPing;
public void AddExtension(string ext, int version) {
switch (ext.Trim()) {
@ -96,6 +96,9 @@ namespace MCGalaxy {
case CpeExt.ExtEntityPositions:
ExtEntityPositions = version;
hasExtPositions = true; break;
case CpeExt.TwoWayPing:
TwoWayPing = version;
hasTwoWayPing = true; break;
}
}
@ -126,6 +129,7 @@ namespace MCGalaxy {
case CpeExt.PlayerClick: return PlayerClick == version;
case CpeExt.EntityProperty: return EntityProperty == version;
case CpeExt.ExtEntityPositions: return ExtEntityPositions == version;
case CpeExt.TwoWayPing: return TwoWayPing == version;
default: return false;
}
}
@ -253,6 +257,7 @@ namespace MCGalaxy {
public const string PlayerClick = "PlayerClick";
public const string EntityProperty = "EntityProperty";
public const string ExtEntityPositions = "ExtEntityPositions";
public const string TwoWayPing = "TwoWayPing";
}
public enum CpeMessageType : byte {

View File

@ -58,6 +58,7 @@ namespace MCGalaxy {
public string truename;
internal bool dontmindme = false;
INetworkSocket socket;
public PingList Ping = new PingList();
public DateTime LastAction, AFKCooldown;
public bool IsAfk = false, AutoAfk;

View File

@ -230,6 +230,7 @@ namespace MCGalaxy {
case Opcode.CpeCustomBlockSupportLevel: return 2;
case Opcode.CpePlayerClick: return 15;
case Opcode.Ping: return 1;
case Opcode.CpeTwoWayPing: return 4;
default:
if (!dontmindme) {
@ -262,6 +263,8 @@ namespace MCGalaxy {
customBlockSupportLevel = buffer[1]; break;
case Opcode.CpePlayerClick:
HandlePlayerClicked(buffer); break;
case Opcode.CpeTwoWayPing:
HandleTwoWayPing(buffer); break;
}
}

View File

@ -55,7 +55,7 @@ namespace MCGalaxy {
}
void SendCpeExtensions() {
Send(Packet.ExtInfo(25), true);
Send(Packet.ExtInfo(26), true);
Send(Packet.ExtEntry(CpeExt.EnvMapAppearance, 1), true); // fix for classicube client, doesn't reply if only send EnvMapAppearance with version 2
Send(Packet.ExtEntry(CpeExt.ClickDistance, 1), true);
@ -90,6 +90,7 @@ namespace MCGalaxy {
Send(Packet.ExtEntry(CpeExt.EntityProperty, 1), true);
Send(Packet.ExtEntry(CpeExt.ExtEntityPositions, 1), true);
Send(Packet.ExtEntry(CpeExt.TwoWayPing, 1), true);
}
void CompleteLoginProcess() {

View File

@ -95,7 +95,12 @@ namespace MCGalaxy.Tasks {
internal static void CheckState(SchedulerTask task) {
Player[] players = PlayerInfo.Online.Items;
foreach (Player p in players) {
p.Send(Packet.Ping());
if (p.hasTwoWayPing) {
p.Send(Packet.TwoWayPing(true, p.Ping.NextTwoWayPingData()));
} else {
p.Send(Packet.Ping());
}
if (Server.afkminutes <= 0) return;
if (DateTime.UtcNow < p.AFKCooldown) return;