diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj
index d970c467..f33fad8e 100644
--- a/Torch.Server/Torch.Server.csproj
+++ b/Torch.Server/Torch.Server.csproj
@@ -277,7 +277,9 @@
+
+
@@ -291,9 +293,15 @@
CharacterView.xaml
+
+ FactionView.xaml
+
FloatingObjectsView.xaml
+
+ PlayerView.xaml
+
LogEventViewer.xaml
@@ -470,11 +478,13 @@
MSBuild:Compile
Designer
+
Designer
MSBuild:Compile
+
diff --git a/Torch.Server/ViewModels/Entities/FactionViewModel.cs b/Torch.Server/ViewModels/Entities/FactionViewModel.cs
new file mode 100644
index 00000000..7ea66fed
--- /dev/null
+++ b/Torch.Server/ViewModels/Entities/FactionViewModel.cs
@@ -0,0 +1,65 @@
+using System.Collections.ObjectModel;
+using Sandbox.Game.World;
+using VRage.Game;
+
+namespace Torch.Server.ViewModels.Entities
+{
+ public class FactionViewModel : ViewModel
+ {
+ public FactionViewModel()
+ { }
+
+ public MyFaction Faction { get; }
+
+ public FactionViewModel(MyFaction faction)
+ {
+ Faction = faction;
+ GenerateMembers();
+ }
+
+ public string Name => Faction.Name;
+ public string Description => Faction.Description;
+ public string Tag => Faction.Tag;
+ public long ID => Faction.FactionId;
+
+ public MemberData Founder { get; private set; }
+ public ObservableCollection Leaders { get; } = new ObservableCollection();
+ public ObservableCollection Members { get; } = new ObservableCollection();
+
+ public void GenerateMembers()
+ {
+ Leaders.Clear();
+ Members.Clear();
+ foreach (MyFactionMember factionMember in Faction.Members.Values)
+ {
+ var playerIdent = MySession.Static.Players.TryGetIdentity(factionMember.PlayerId);
+ MySession.Static.Players.TryGetPlayerId(factionMember.PlayerId, out MyPlayer.PlayerId playerId);
+ MemberData md = new MemberData
+ {
+ PlayerIdent = playerIdent,
+ PlayerId = playerId
+ };
+
+ if (factionMember.IsFounder)
+ {
+ Founder = md;
+ continue;
+ }
+
+ if (factionMember.IsLeader)
+ {
+ Leaders.Add(md);
+ continue;
+ }
+
+ Members.Add(md);
+ }
+ }
+ }
+
+ public sealed class MemberData
+ {
+ public MyIdentity PlayerIdent { get; set; }
+ public MyPlayer.PlayerId PlayerId { get; set; }
+ }
+}
diff --git a/Torch.Server/ViewModels/Entities/PlayerViewModel.cs b/Torch.Server/ViewModels/Entities/PlayerViewModel.cs
new file mode 100644
index 00000000..858b671a
--- /dev/null
+++ b/Torch.Server/ViewModels/Entities/PlayerViewModel.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Sandbox.Game.World;
+
+namespace Torch.Server.ViewModels.Entities
+{
+ public class PlayerViewModel : ViewModel
+ {
+ private readonly MyIdentity _backing;
+ private readonly MyPlayer.PlayerId _backingPlayer;
+
+ public PlayerViewModel()
+ { }
+
+ public MyIdentity Player => _backing;
+
+ public PlayerViewModel(MyIdentity player, MyPlayer.PlayerId playerId)
+ {
+ _backing = player;
+ _backingPlayer = playerId;
+ }
+
+ public string Name => Player.DisplayName;
+ public long ID => Player.IdentityId;
+ public ulong SteamID => _backingPlayer.SteamId;
+
+ public string FactionTag
+ {
+ get
+ {
+ var faction = MySession.Static.Factions.GetPlayerFaction(ID);
+ return faction is null ? string.Empty : faction.Tag;
+ }
+ }
+
+ public string FactionName
+ {
+ get
+ {
+ var faction = MySession.Static.Factions.GetPlayerFaction(ID);
+ return faction is null ? string.Empty : faction.Name;
+ }
+ }
+ public DateTime LastLogin => Player.LastLoginTime;
+ public DateTime LastLogout => Player.LastLogoutTime;
+ public string LastDeathLocation => Player.LastDeathPosition.ToString();
+ public int BlocksBuilt => Player.BlockLimits.BlocksBuilt;
+ public int PCU => Player.BlockLimits.PCUBuilt;
+ public bool OverLimits => Player.BlockLimits.IsOverLimits;
+ }
+}
\ No newline at end of file
diff --git a/Torch.Server/ViewModels/EntityTreeViewModel.cs b/Torch.Server/ViewModels/EntityTreeViewModel.cs
index a39129c4..e4731915 100644
--- a/Torch.Server/ViewModels/EntityTreeViewModel.cs
+++ b/Torch.Server/ViewModels/EntityTreeViewModel.cs
@@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Controls;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Character;
using Torch.Server.ViewModels.Entities;
-using VRage.Game.ModAPI;
-using VRage.ModAPI;
using System.Windows.Threading;
using NLog;
+using Sandbox.Game.Multiplayer;
+using Sandbox.Game.World;
+using Torch.API;
+using Torch.API.Managers;
+using Torch.API.Session;
using Torch.Collections;
-using Torch.Server.Views.Entities;
+using VRage.Game.ModAPI;
+using PlayerViewModel = Torch.Server.ViewModels.Entities.PlayerViewModel;
namespace Torch.Server.ViewModels
{
@@ -34,6 +35,8 @@ public enum SortEnum
public MtObservableSortedDictionary Characters { get; set; } = new MtObservableSortedDictionary();
public MtObservableSortedDictionary FloatingObjects { get; set; } = new MtObservableSortedDictionary();
public MtObservableSortedDictionary VoxelMaps { get; set; } = new MtObservableSortedDictionary();
+ public MtObservableSortedDictionary Players { get; set; } = new MtObservableSortedDictionary();
+ public MtObservableSortedDictionary Factions { get; set; } = new MtObservableSortedDictionary();
public Dispatcher ControlDispatcher => _control.Dispatcher;
public SortedView SortedGrids { get; }
@@ -41,6 +44,8 @@ public enum SortEnum
public SortedView SortedCharacters { get; }
public SortedView SortedFloatingObjects { get; }
public SortedView SortedVoxelMaps { get; }
+ public SortedView SortedPlayers { get; }
+ public SortedView SortedFactions { get; }
private EntityViewModel _currentEntity;
private SortEnum _currentSort;
@@ -58,20 +63,105 @@ public SortEnum CurrentSort
set => SetValue(ref _currentSort, value);
}
- // I hate you today WPF
- public EntityTreeViewModel() : this(null)
+ // Westin miller still hates you today WPF
+ public EntityTreeViewModel() : this(null, null)
{
}
- public EntityTreeViewModel(UserControl control)
+ public EntityTreeViewModel(UserControl control, ITorchServer server)
{
_control = control;
- var comparer = new EntityViewModel.Comparer(_currentSort);
- SortedGrids = new SortedView(Grids.Values, comparer);
- FilteredSortedGrids = new SortedView(Grids.Values, comparer);
- SortedCharacters = new SortedView(Characters.Values, comparer);
- SortedFloatingObjects = new SortedView(FloatingObjects.Values, comparer);
- SortedVoxelMaps = new SortedView(VoxelMaps.Values, comparer);
+ var entityComparer = new EntityViewModel.Comparer(_currentSort);
+ SortedGrids = new SortedView(Grids.Values, entityComparer);
+ FilteredSortedGrids = new SortedView(Grids.Values, entityComparer);
+ SortedCharacters = new SortedView(Characters.Values, entityComparer);
+ SortedFloatingObjects = new SortedView(FloatingObjects.Values, entityComparer);
+ SortedVoxelMaps = new SortedView(VoxelMaps.Values, entityComparer);
+ SortedPlayers = new SortedView(Players.Values, Comparer
+ .Create((x, y) =>
+ string.Compare(x?.Name, y?.Name, StringComparison.InvariantCultureIgnoreCase))
+ );
+ SortedFactions = new SortedView(Factions.Values, Comparer
+ .Create((x, y) =>
+ string.Compare(x?.Name, y?.Name, StringComparison.InvariantCultureIgnoreCase))
+ );
+
+ if (server != null)
+ {
+ var sessionManager = server.Managers.GetManager();
+ sessionManager.SessionStateChanged += RegisterLiveNonEntities;
+ }
+ }
+
+ private void RegisterLiveNonEntities(ITorchSession session, TorchSessionState newState)
+ {
+ switch (newState)
+ {
+ case TorchSessionState.Loaded:
+ foreach (var identity in MySession.Static.Players.GetAllPlayers())
+ {
+ if (identity.SteamId == 0) continue;
+ var player = MySession.Static.Players.TryGetPlayerIdentity(identity.SteamId);
+ if (player is null) continue;
+ Players.Add(new KeyValuePair(player.IdentityId, new PlayerViewModel(player, identity)));
+ }
+
+ foreach (MyFaction faction in MySession.Static.Factions.GetAllFactions())
+ {
+ Factions.Add(new KeyValuePair(faction.FactionId, new FactionViewModel(faction)));
+ }
+
+ Sync.Players.RealPlayerIdentityCreated += NewPlayerCreated;
+ MySession.Static.Factions.FactionCreated += NewFactionCreated;
+ MySession.Static.Factions.FactionStateChanged += FactionChanged;
+ break;
+
+ case TorchSessionState.Unloading:
+ Sync.Players.RealPlayerIdentityCreated -= NewPlayerCreated;
+ MySession.Static.Factions.FactionCreated -= NewFactionCreated;
+ Players.Clear();
+ break;
+ }
+ }
+
+ // These might be off, but I only need the reason and main faction id.
+ private void FactionChanged(MyFactionStateChange reason, long FactionId, long ToFactionId, long PlayerId, long SenderId)
+ {
+ switch (reason)
+ {
+ case MyFactionStateChange.RemoveFaction:
+ ControlDispatcher.Invoke(() => { Factions.Remove(FactionId);});
+
+ break;
+ case MyFactionStateChange.FactionMemberAcceptJoin:
+ case MyFactionStateChange.FactionMemberPromote:
+ case MyFactionStateChange.FactionMemberKick:
+ case MyFactionStateChange.FactionMemberDemote:
+ case MyFactionStateChange.FactionMemberLeave:
+ ControlDispatcher.Invoke(() =>
+ {
+ if (Factions.TryGetValue(FactionId, out FactionViewModel faction))
+ faction.GenerateMembers();
+ });
+ break;
+ }
+ }
+
+ private void NewFactionCreated(long id)
+ {
+ ControlDispatcher.Invoke(() =>
+ {
+ var faction = MySession.Static.Factions.GetPlayerFaction(id);
+ if (faction is null) return;
+ Factions.Add(new KeyValuePair(faction.FactionId, new FactionViewModel(faction)));
+ });
+ }
+
+ private void NewPlayerCreated(long identityId)
+ {
+ var player = MySession.Static.Players.TryGetPlayer(identityId);
+ if (player is null) return;
+ Players.Add(new KeyValuePair(player.Identity.IdentityId, new PlayerViewModel(player.Identity, new MyPlayer.PlayerId())));
}
public void Init()
diff --git a/Torch.Server/Views/Entities/FactionView.xaml b/Torch.Server/Views/Entities/FactionView.xaml
new file mode 100644
index 00000000..efa1ff17
--- /dev/null
+++ b/Torch.Server/Views/Entities/FactionView.xaml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Torch.Server/Views/Entities/FactionView.xaml.cs b/Torch.Server/Views/Entities/FactionView.xaml.cs
new file mode 100644
index 00000000..3e7f186a
--- /dev/null
+++ b/Torch.Server/Views/Entities/FactionView.xaml.cs
@@ -0,0 +1,99 @@
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using Sandbox.ModAPI;
+using Torch.Server.ViewModels.Entities;
+
+namespace Torch.Server.Views.Entities
+{
+ public partial class FactionView : UserControl
+ {
+ private readonly FactionViewModel _vm;
+ private MemberData _selectedMember;
+ private MemberData _selectedLeader;
+
+ public FactionView(FactionViewModel vm)
+ {
+ InitializeComponent();
+ _vm = vm;
+ DataContext = _vm;
+ }
+
+ private void PromoteLeader(object sender, RoutedEventArgs e)
+ {
+ if (_selectedLeader is null) return;
+
+ // Need to remove him, or we end up with 2 founders.
+ var CurrentFounder = _vm.Founder;
+ var NewFounder = _selectedLeader;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() =>
+ {
+ _vm.Faction.PromoteToFounder(NewFounder.PlayerIdent.IdentityId);
+ _vm.Faction.DemoteMember(CurrentFounder.PlayerIdent.IdentityId);
+ });
+ _selectedLeader = null;
+ _vm.GenerateMembers();
+ }
+
+ private void PromoteMember(object sender, RoutedEventArgs e)
+ {
+ if (_selectedMember is null) return;
+ var member = _selectedMember;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.PromoteMember(member.PlayerIdent.IdentityId); });
+ _selectedMember = null;
+ _vm.GenerateMembers();
+ }
+
+ private void DemoteLeader(object sender, RoutedEventArgs e)
+ {
+ if (_selectedLeader is null) return;
+ var member = _selectedLeader;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.DemoteMember(member.PlayerIdent.IdentityId); });
+ _selectedLeader = null;
+ _vm.GenerateMembers();
+ }
+
+ private void KickLeader(object sender, RoutedEventArgs e)
+ {
+ if (_selectedLeader is null) return;
+ var member = _selectedLeader;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.KickMember(member.PlayerIdent.IdentityId); });
+ _selectedLeader = null;
+ _vm.GenerateMembers();
+ }
+
+ private void KickMember(object sender, RoutedEventArgs e)
+ {
+ if (_selectedMember is null) return;
+ var member = _selectedMember;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.KickMember(member.PlayerIdent.IdentityId); });
+ _selectedMember = null;
+ _vm.GenerateMembers();
+ }
+
+ private void RemoveFaction(object sender, RoutedEventArgs e)
+ {
+ _selectedLeader = null;
+ _selectedMember = null;
+ var members = _vm.Faction.Members.Keys.ToList();
+ foreach (long factionMember in members)
+ {
+ if (_vm.Faction.FounderId == factionMember) continue;
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.KickMember(factionMember); });
+ }
+
+ MyAPIGateway.Utilities.InvokeOnGameThread(() => { _vm.Faction.KickMember(_vm.Faction.FounderId); });
+ MyAPIGateway.Session.Factions.RemoveFaction(_vm.Faction.FactionId);
+ }
+
+ private void NewSelectedMember(object sender, SelectionChangedEventArgs e)
+ {
+ if (MembersList.SelectedItem is MemberData member) _selectedMember = member;
+ }
+
+ private void NewSelectedLeader(object sender, SelectionChangedEventArgs e)
+ {
+ if (LeadersList.SelectedItem is MemberData member) _selectedLeader = member;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Torch.Server/Views/Entities/PlayerView.xaml b/Torch.Server/Views/Entities/PlayerView.xaml
new file mode 100644
index 00000000..fa00de45
--- /dev/null
+++ b/Torch.Server/Views/Entities/PlayerView.xaml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Torch.Server/Views/Entities/PlayerView.xaml.cs b/Torch.Server/Views/Entities/PlayerView.xaml.cs
new file mode 100644
index 00000000..c8385e7d
--- /dev/null
+++ b/Torch.Server/Views/Entities/PlayerView.xaml.cs
@@ -0,0 +1,22 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Torch.Server.Views.Entities
+{
+ public partial class PlayerView : UserControl
+ {
+ public PlayerView()
+ {
+ InitializeComponent();
+
+ ThemeControl.UpdateDynamicControls += UpdateResourceDict;
+ UpdateResourceDict(ThemeControl.currentTheme);
+ }
+
+ public void UpdateResourceDict(ResourceDictionary dictionary)
+ {
+ Resources.MergedDictionaries.Clear();
+ Resources.MergedDictionaries.Add(dictionary);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Torch.Server/Views/EntitiesControl.xaml b/Torch.Server/Views/EntitiesControl.xaml
index a5fe656b..90260e9b 100644
--- a/Torch.Server/Views/EntitiesControl.xaml
+++ b/Torch.Server/Views/EntitiesControl.xaml
@@ -15,7 +15,7 @@
-
+
@@ -99,7 +99,7 @@
-
+
@@ -115,7 +115,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Torch.Server/Views/EntitiesControl.xaml.cs b/Torch.Server/Views/EntitiesControl.xaml.cs
index 261c5840..d6407e40 100644
--- a/Torch.Server/Views/EntitiesControl.xaml.cs
+++ b/Torch.Server/Views/EntitiesControl.xaml.cs
@@ -1,21 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Collections.Specialized;
using System.Linq;
-using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
using NLog;
-using Sandbox.Game.Entities;
-using Torch.Collections;
using Torch.Server.ViewModels;
using Torch.Server.ViewModels.Blocks;
using Torch.Server.ViewModels.Entities;
@@ -31,13 +20,15 @@ namespace Torch.Server.Views
public partial class EntitiesControl : UserControl
{
public EntityTreeViewModel Entities { get; set; }
+ private TorchServer _server;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
- public EntitiesControl()
+ public EntitiesControl(TorchServer server)
{
InitializeComponent();
- Entities = new EntityTreeViewModel(this);
+ _server = server;
+ Entities = new EntityTreeViewModel(this, _server);
DataContext = Entities;
Entities.Init();
SortCombo.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
@@ -46,11 +37,17 @@ public EntitiesControl()
SortCombo_Voxels.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
SortCombo_FloatingObjects.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
- FilterBox.TextChanged += (sender, args) =>
+ GridsFilterBox.TextChanged += (sender, args) =>
{
- string filter = FilterBox.Text;
+ string filter = GridsFilterBox.Text;
Entities.FilteredSortedGrids.Filter = grid => grid.Name.Contains(filter, StringComparison.OrdinalIgnoreCase);
};
+
+ PlayersFilterBox.TextChanged += (sender, args) =>
+ {
+ string filter = PlayersFilterBox.Text;
+ Entities.SortedPlayers.Filter = player => player.Name.Contains(filter, StringComparison.OrdinalIgnoreCase);
+ };
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs
-
-
-
+
diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs
index a0ac963a..3e09d1a3 100644
--- a/Torch.Server/Views/TorchUI.xaml.cs
+++ b/Torch.Server/Views/TorchUI.xaml.cs
@@ -64,11 +64,6 @@ public TorchUI(TorchServer server)
AttachConsole();
- //Left = _config.WindowPosition.X;
- //Top = _config.WindowPosition.Y;
- //Width = _config.WindowSize.X;
- //Height = _config.WindowSize.Y;
-
Chat.BindServer(server);
PlayerList.BindServer(server);
Plugins.BindServer(server);
@@ -78,6 +73,8 @@ public TorchUI(TorchServer server)
Themes.SetConfig(_config);
Title = $"{_config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
Instance = this;
+
+ EntityManagerTab.Content = new EntitiesControl(_server);
Loaded += TorchUI_Loaded;
}