Korero – Rev 1
?pathlinks?
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Korero.Chat;
using Korero.Communication;
using Korero.Profile;
using Korero.Properties;
using Korero.Serialization;
using Korero.Teleport;
using Korero.Utilities;
using Serilog;
namespace Korero.Land
{
public partial class LandForm : Form
{
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly MainForm _mainForm;
private readonly SolidBrush _mapAvatarPositionBrushOuter;
private readonly Pen _mapAvatarPositionBrushSelect;
private readonly ConcurrentDictionary<string, MapAvatarPosition> _mapAvatarPositions;
private readonly MqttCommunication _mqttCommunication;
private AvatarProfileForm _avatarProfileForm;
private SolidBrush _mapAvatarPositionBrush;
private int _mapAvatarPositionDiameter;
private int _mapAvatarPositionOuterDiameter;
private int _mapAvatarPositionSelectDiameter;
private volatile Image _mapImage;
private volatile Image _mapImageAvatars;
private TeleportForm _teleportForm;
#endregion
#region Constructors, Destructors and Finalizers
public LandForm()
{
InitializeComponent();
Utilities.WindowState.FormTracker.Track(this);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
Settings.Default.PropertyChanged += Default_PropertyChanged;
_mapAvatarPositionDiameter = Settings.Default.MapAvatarDotMultiplier * 8;
_mapAvatarPositionOuterDiameter = Settings.Default.MapAvatarDotMultiplier * 10;
_mapAvatarPositionSelectDiameter = Settings.Default.MapAvatarDotMultiplier * 12;
_mapAvatarPositions = new ConcurrentDictionary<string, MapAvatarPosition>();
_mapAvatarPositionBrush = new SolidBrush(Settings.Default.MapDotColor);
_mapAvatarPositionBrushOuter = new SolidBrush(Color.Black);
_mapAvatarPositionBrushSelect = new Pen(Color.Red);
}
public LandForm(MainForm mainForm, MqttCommunication mqttCommunication) : this()
{
_mainForm = mainForm;
_mqttCommunication = mqttCommunication;
_mqttCommunication.NotificationReceived += MqttCommunication_NotificationReceived;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
components.Dispose();
}
Settings.Default.PropertyChanged -= Default_PropertyChanged;
_mqttCommunication.NotificationReceived -= MqttCommunication_NotificationReceived;
base.Dispose(disposing);
}
#endregion
#region Event Handlers
private void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
_mapAvatarPositionDiameter = Settings.Default.MapAvatarDotMultiplier * 8;
_mapAvatarPositionOuterDiameter = Settings.Default.MapAvatarDotMultiplier * 10;
_mapAvatarPositionSelectDiameter = Settings.Default.MapAvatarDotMultiplier * 12;
_mapAvatarPositionBrush = new SolidBrush(Settings.Default.MapDotColor);
}
private void MqttCommunication_NotificationReceived(object sender, MqttNotificationEventArgs e)
{
switch (e.Notification["notification"])
{
case "statistics":
var statistics = e.Notification["statistics"];
if (string.IsNullOrEmpty(statistics))
{
break;
}
var metrics = new CSV(statistics).ToArray();
for (var i = 0; i < metrics.Length; ++i)
{
var value = metrics.ElementAtOrDefault(i + 1);
switch (metrics[i])
{
case "ScriptedObjects":
scriptedObjectsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "Objects":
objectsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "FPS":
fpsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "PhysicsFPS":
physicsFpsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "ActiveScripts":
activeScriptsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "ScriptTime":
scriptTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "FrameTime":
frameTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "Dilation":
timeDilationTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "LastLag":
lastLagTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "Agents":
agentsTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "PhysicsTime":
physicsTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
case "NetTime":
netTimeTextBox.InvokeIfRequired(textBox => { textBox.Text = value; });
break;
}
}
break;
case "map":
var region = e.Notification["region"];
textBox2.InvokeIfRequired(textBox => { textBox.Text = region; });
var mapImageBase64 = e.Notification["texture"];
if (string.IsNullOrEmpty(mapImageBase64))
{
return;
}
var mapBytes = Convert.FromBase64String(mapImageBase64);
if (mapBytes.Length == 0)
{
return;
}
using (var memoryStream = new MemoryStream(mapBytes))
{
if (_mapImage != null)
{
_mapImage.Dispose();
_mapImage = null;
}
_mapImage = Image.FromStream(memoryStream);
}
break;
case "parcel":
var parcelName = e.Notification["name"];
var description = e.Notification["description"];
textBox1.InvokeIfRequired(textBox => { textBox.Text = parcelName; });
richTextBox2.InvokeIfRequired(textBox => { textBox.Text = description; });
break;
case "coarse":
var oldAvatars = e.Notification["old"];
foreach (var avatar in CsvStride(oldAvatars, 2))
{
var name = avatar[0];
var id = avatar[1];
listBox1.InvokeIfRequired(listBox =>
{
if (listBox.Items.OfType<string>().Any(item => item == name))
{
listBox.Items.Remove(name);
}
});
}
var newAvatars = e.Notification["new"];
foreach (var avatar in CsvStride(newAvatars, 3))
{
var name = avatar[0];
var id = avatar[1];
var position = avatar[2];
listBox1.InvokeIfRequired(listBox =>
{
if (listBox.Items.OfType<string>().All(item => item != name))
{
listBox.Items.Add(name);
}
});
}
break;
}
}
private void ChatToolStripMenuItem_Click(object sender, EventArgs e)
{
var index = listBox1.SelectedIndex;
if (index == -1)
{
return;
}
var selectedItem = (string) listBox1.SelectedItem;
if (_mainForm.ChatForm != null)
{
_mainForm.ChatForm.CreateOrSelect(selectedItem);
return;
}
_mainForm.ChatForm = new ChatForm(_mainForm, _mqttCommunication);
_mainForm.ChatForm.Closing += ChatForm_Closing;
_mainForm.ChatForm.Show();
_mainForm.ChatForm.CreateOrSelect(selectedItem);
}
private void ChatForm_Closing(object sender, CancelEventArgs e)
{
if (_mainForm.ChatForm == null)
{
return;
}
_mainForm.ChatForm.Closing -= ChatForm_Closing;
_mainForm.ChatForm.Dispose();
_mainForm.ChatForm = null;
}
private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var listBox = (ListBox) sender;
var index = listBox.SelectedIndex;
if (index == -1)
{
return;
}
var item = (string) listBox.SelectedItem;
if (!_mapAvatarPositions.TryGetValue(item, out var selectedMapAvatarPosition))
{
return;
}
if (_mapImageAvatars == null)
{
return;
}
var bitmap = new Bitmap(_mapImageAvatars.Width, _mapImageAvatars.Height);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.DrawImage(_mapImageAvatars, Point.Empty);
foreach (var mapAvatarPosition in _mapAvatarPositions.Values)
{
graphics.FillEllipse(_mapAvatarPositionBrushOuter,
mapAvatarPosition.X - _mapAvatarPositionOuterDiameter / 2,
_mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionOuterDiameter / 2,
_mapAvatarPositionOuterDiameter, _mapAvatarPositionOuterDiameter);
graphics.FillEllipse(_mapAvatarPositionBrush, mapAvatarPosition.X - _mapAvatarPositionDiameter / 2,
_mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionDiameter / 2,
_mapAvatarPositionDiameter, _mapAvatarPositionDiameter);
}
graphics.DrawEllipse(_mapAvatarPositionBrushSelect,
selectedMapAvatarPosition.X - _mapAvatarPositionSelectDiameter / 2,
_mapImageAvatars.Height - selectedMapAvatarPosition.Y - _mapAvatarPositionSelectDiameter / 2,
_mapAvatarPositionSelectDiameter, _mapAvatarPositionSelectDiameter);
}
_mapImageAvatars = bitmap;
pictureBox1.InvokeIfRequired(pictureBox =>
{
if (pictureBox.Image != null)
{
pictureBox.Image.Dispose();
pictureBox.Image = null;
}
pictureBox.Image = bitmap;
});
}
private async void FlyToToolStripMenuItem_Click(object sender, EventArgs e)
{
var index = listBox1.SelectedIndex;
if (index == -1)
{
return;
}
var selectedItem = (string) listBox1.SelectedItem;
if (!_mapAvatarPositions.TryGetValue(selectedItem, out var mapAvatarPosition))
{
return;
}
var data = new Dictionary<string, string>
{
{"command", "flyto"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"position", $"<{mapAvatarPosition.X}, {mapAvatarPosition.Y}, {mapAvatarPosition.Z}>"},
{"vicinity", $"{Settings.Default.FlyToVicinity}"},
{"duration", $"{Settings.Default.FlyToDuration}"}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
}
}
private async void TeleportToToolStripMenuItem_Click(object sender, EventArgs e)
{
var index = listBox1.SelectedIndex;
if (index == -1)
{
return;
}
var selectedItem = (string) listBox1.SelectedItem;
if (!_mapAvatarPositions.TryGetValue(selectedItem, out var mapAvatarPosition))
{
return;
}
var data = new Dictionary<string, string>
{
{"command", "getregiondata"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"data", "Name"}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
return;
}
var region = callback["Name"].FirstOrDefault();
if (string.IsNullOrEmpty(region))
{
return;
}
data = new Dictionary<string, string>
{
{"command", "teleport"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"entity", "region"},
{"region", region},
{"position", $"<{mapAvatarPosition.X}, {mapAvatarPosition.Y}, {mapAvatarPosition.Z}>"},
{"fly", "True"}
};
callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
}
}
private void TeleportToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_teleportForm != null)
{
return;
}
_teleportForm = new TeleportForm(_mainForm, _mqttCommunication);
_teleportForm.Closing += TeleportForm_Closing;
_teleportForm.Show();
}
private void TeleportForm_Closing(object sender, CancelEventArgs e)
{
if (_teleportForm == null)
{
return;
}
_teleportForm.Closing -= TeleportForm_Closing;
_teleportForm.Dispose();
_teleportForm = null;
}
private void ProfileToolStripMenuItem_Click(object sender, EventArgs e)
{
var index = listBox1.SelectedIndex;
if (index == -1)
{
return;
}
var selectedItem = (string) listBox1.SelectedItem;
if (_avatarProfileForm != null)
{
return;
}
_avatarProfileForm = new AvatarProfileForm(_mainForm, selectedItem, _mqttCommunication);
_avatarProfileForm.Closing += AvatarProfileForm_Closing;
_avatarProfileForm.Show();
}
private void AvatarProfileForm_Closing(object sender, CancelEventArgs e)
{
if (_avatarProfileForm == null)
{
return;
}
_avatarProfileForm.Closing -= AvatarProfileForm_Closing;
_avatarProfileForm.Dispose();
_avatarProfileForm = null;
}
private void RichTextBox2_LinkClicked(object sender, LinkClickedEventArgs e)
{
Process.Start(e.LinkText);
}
private void LandForm_FormClosing(object sender, FormClosingEventArgs e)
{
_cancellationTokenSource.Cancel();
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
pictureBox1.Image = null;
}
}
private async void OfferTeleportToolStripMenuItem_Click(object sender, EventArgs e)
{
var index = listBox1.SelectedIndex;
if (index == -1)
{
return;
}
var selectedItem = (string) listBox1.SelectedItem;
var firstName = selectedItem.Split(' ')[0];
var lastName = selectedItem.Split(' ')[1];
var data = new Dictionary<string, string>
{
{"command", "lure"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"firstname", firstName},
{"lastname", lastName}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
}
}
private async void SetHomeToolStripMenuItem_Click(object sender, EventArgs e)
{
var data = new Dictionary<string, string>
{
{"command", "sethome"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
toolStripStatusLabel1.Text = "Could not set home.";
return;
}
toolStripStatusLabel1.Text = "Home set.";
}
private async void GoHomeToolStripMenuItem_Click(object sender, EventArgs e)
{
var data = new Dictionary<string, string>
{
{"command", "gohome"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
toolStripStatusLabel1.Text = "Could not teleport home.";
return;
}
toolStripStatusLabel1.Text = "Teleported home.";
}
private async void LandForm_Shown(object sender, EventArgs e)
{
await Task.WhenAll(LoadMap(), GetAvatars(), GetParcelDetails(), CreateMapImageAvatarOverlay());
}
#endregion
#region Private Methods
private async Task CreateMapImageAvatarOverlay()
{
try
{
do
{
try
{
await Task.Delay(1000, _cancellationToken);
if (_mapImage == null)
{
continue;
}
var bitmap = new Bitmap(_mapImage.Width, _mapImage.Height);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.DrawImage(_mapImage, Point.Empty);
foreach (var mapAvatarPosition in _mapAvatarPositions.Values)
{
graphics.FillEllipse(_mapAvatarPositionBrushOuter,
mapAvatarPosition.X - _mapAvatarPositionOuterDiameter / 2,
_mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionOuterDiameter / 2,
_mapAvatarPositionOuterDiameter, _mapAvatarPositionOuterDiameter);
graphics.FillEllipse(_mapAvatarPositionBrush,
mapAvatarPosition.X - _mapAvatarPositionDiameter / 2,
_mapImage.Height - mapAvatarPosition.Y - _mapAvatarPositionDiameter / 2,
_mapAvatarPositionDiameter, _mapAvatarPositionDiameter);
listBox1.InvokeIfRequired(listBox =>
{
if (listBox.SelectedIndex == -1)
{
return;
}
if ((string) listBox.SelectedItem == mapAvatarPosition.Name)
{
graphics.DrawEllipse(_mapAvatarPositionBrushSelect,
mapAvatarPosition.X - _mapAvatarPositionSelectDiameter / 2,
_mapImageAvatars.Height - mapAvatarPosition.Y -
_mapAvatarPositionSelectDiameter / 2, _mapAvatarPositionSelectDiameter,
_mapAvatarPositionSelectDiameter);
}
});
}
}
if (_mapImageAvatars != null)
{
_mapImageAvatars?.Dispose();
_mapImageAvatars = null;
}
_mapImageAvatars = bitmap;
pictureBox1.InvokeIfRequired(pictureBox =>
{
if (pictureBox.Image != null)
{
pictureBox.Image.Dispose();
pictureBox.Image = null;
}
pictureBox.Image = bitmap;
});
}
catch (Exception ex)
{
Log.Warning(ex, "Unable to create and populate map and avatar image.");
}
} while (!_cancellationToken.IsCancellationRequested);
}
catch (Exception ex) when (ex is ObjectDisposedException || ex is OperationCanceledException)
{
}
catch (Exception ex)
{
Log.Warning(ex, "Map and avatar image thread failure.");
}
}
private static IEnumerable<string[]> CsvStride(string input, int stride)
{
var i = 0;
return new CSV(input).Select(s => new {s, num = i++})
.GroupBy(t => t.num / stride, t => t.s)
.Select(g => g.ToArray());
}
private async Task GetParcelDetails()
{
var data = new Dictionary<string, string>
{
{"command", "getparceldata"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"data", new CSV(new[] {"Name", "Desc"})}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
return;
}
textBox1.InvokeIfRequired(textBox => { textBox.Text = callback["Name"].FirstOrDefault(); });
richTextBox2.InvokeIfRequired(textBox => { textBox.Text = callback["Desc"].FirstOrDefault(); });
}
private async Task GetAvatars()
{
try
{
do
{
await Task.Delay(1000, _cancellationToken);
if (!_mqttCommunication.IsConnected)
{
continue;
}
var data = new Dictionary<string, string>
{
{"command", "getavatarpositions"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"entity", "region"}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
continue;
}
// Add avatars.
_mapAvatarPositions.Clear();
foreach (var tuple in CsvStride(callback.Data, 3))
{
var name = tuple[0];
var id = tuple[1];
var position = tuple[2];
// Update avatar list.
listBox1.InvokeIfRequired(listBox =>
{
if (listBox.Items.OfType<string>().All(item => item != name))
{
listBox.Items.Add(name);
}
});
var vector = position.Split('<', '>', ',', ' ')
.Where(segment => !string.IsNullOrEmpty(segment)).ToArray();
if (!float.TryParse(vector[0], out var x) ||
!float.TryParse(vector[1], out var y) ||
!float.TryParse(vector[2], out var z))
{
continue;
}
_mapAvatarPositions.TryAdd(name, new MapAvatarPosition(name, x, y, z));
}
// Remove avatars that are not there anymore.
listBox1.InvokeIfRequired(listBox =>
{
var remove = new List<string>();
foreach (var avatar in listBox.Items.OfType<string>())
{
if (!_mapAvatarPositions.ContainsKey(avatar))
{
remove.Add(avatar);
}
}
foreach (var avatar in remove)
{
listBox.Items.Remove(avatar);
}
});
} while (!_cancellationToken.IsCancellationRequested);
}
catch (Exception ex) when (ex is ObjectDisposedException || ex is OperationCanceledException)
{
}
catch (Exception ex)
{
Log.Warning(ex, "Error while retrieving avatars.");
}
}
private async Task LoadMap()
{
var data = new Dictionary<string, string>
{
{"command", "getgridregiondata"},
{"group", Settings.Default.Group},
{"password", Settings.Default.Password},
{"data", new CSV(new[] {"MapImageID", "Name"})}
};
var callback = await _mqttCommunication.SendCommand(new Command(data), _cancellationToken);
if (callback == null || !callback.Success)
{
if (callback != null)
{
Log.Warning("Command {Command} has failed with {Error}.",
callback.Command, callback.Error);
}
return;
}
var regionName = callback["Name"].FirstOrDefault();
if (!string.IsNullOrEmpty(regionName))
{
textBox2.InvokeIfRequired(textBox => { textBox.Text = regionName; });
}
var callbackMapImageId = callback["MapImageID"].FirstOrDefault();
if (!string.IsNullOrEmpty(callbackMapImageId) && Guid.TryParse(callbackMapImageId, out var mapImageId) &&
mapImageId != Guid.Empty)
{
var mapImage =
await Communication.Utilities.DownloadImage(mapImageId, _mainForm.MqttCommunication,
_cancellationToken);
if (mapImage != null)
{
if (_mapImage != null)
{
_mapImage.Dispose();
_mapImage = null;
}
_mapImage = mapImage;
}
}
}
#endregion
}
}
Generated by GNU Enscript 1.6.5.90.