actually now its usable

This commit is contained in:
zznty
2023-11-13 23:17:39 +07:00
parent aecc7ee66f
commit ce07a1e86a
41 changed files with 1401 additions and 138 deletions

25
Kits/Commands.cs Normal file
View File

@@ -0,0 +1,25 @@
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Commands;
using Torch.Commands.Permissions;
using VRage.Game.ModAPI;
namespace Kits;
public class Commands : CommandModule
{
[Command("kit")]
[Permission(MyPromoteLevel.None)]
public void GetKit(string name)
{
var manager = Context.Torch.CurrentSession.Managers.GetManager<IKitManager>();
var player = (MyPlayer)Context.Player;
if (!manager.CanGiveKit(player, name, out var reason))
{
Context.Respond(reason, "Error");
return;
}
manager.GiveKit(player, player.Character.GetInventoryBase(), name);
Context.Respond($"You have got kit {name}");
}
}

20
Kits/Config.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.Collections.ObjectModel;
using System.Xml.Schema;
using System.Xml.Serialization;
using Kits.Views;
using Torch;
using Torch.Views;
namespace Kits;
[XmlRoot()]
public class Config : ViewModel
{
[Display(Name = "Kits", EditorType = typeof(EditButton))]
[XmlArrayItem("Kit")]
public ObservableCollection<KitViewModel> Kits { get; set; } = new();
[XmlAttribute(Form = XmlSchemaForm.Qualified, Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
// ReSharper disable once InconsistentNaming
public string noNamespaceSchemaLocation = "Kits.v1.0.6.xsd";
}

3
Kits/FodyWeavers.xml Normal file
View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

178
Kits/KitManager.cs Normal file
View File

@@ -0,0 +1,178 @@
using heh;
using net.luckperms.api;
using NLog;
using PetaPoco;
using Sandbox.Game;
using Sandbox.Game.Entities;
using Sandbox.Game.GameSystems.BankingAndCurrency;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.Managers;
using VRage;
using VRage.Game.Entity;
using VRage.Library.Utils;
using VRage.ObjectBuilders;
namespace Kits;
public class KitManager : Manager, IKitManager
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private readonly Config _config;
[Dependency]
private readonly IDbManager _dbManager = null!;
[Dependency]
private readonly MultiplayerManagerDedicated _multiplayerManager = null!;
private IDatabase _db = null!;
public KitManager(ITorchBase torchInstance, Config config) : base(torchInstance)
{
_config = config;
}
public override void Attach()
{
base.Attach();
_db = _dbManager.Create("kits");
MyVisualScriptLogicProvider.RespawnShipSpawned += RespawnShipSpawned;
}
private void RespawnShipSpawned(long shipEntityId, long playerId, string respawnShipPrefabName)
{
if (!MyEntities.TryGetEntityById(shipEntityId, out MyCubeGrid grid) ||
!Sync.Players.TryGetPlayerId(playerId, out var playerClientId) ||
Sync.Players.GetPlayerById(playerClientId) is not { } player)
return;
foreach (var kit in _config.Kits.Where(b => CanGiveRespawnKit(player, b, respawnShipPrefabName, out _)))
{
GiveKit(player, grid.GetFatBlocks().First(b => b is MyCargoContainer or MyCockpit).GetInventoryBase(), kit);
}
}
public void GiveKit(MyPlayer player, MyInventoryBase inventory, string kitName)
{
GiveKit(player, inventory, GetKit(kitName));
}
public bool CanGiveKit(MyPlayer player, string kitName, out string reason)
{
return CanGiveKit(player, GetKit(kitName), out reason);
}
public bool CanGiveRespawnKit(MyPlayer player, string kitName, string respawnName, out string reason)
{
return CanGiveRespawnKit(player, GetKit(kitName), respawnName, out reason);
}
public KitViewModel GetKit(string kitName)
{
return _config.Kits.First(b => b.Name == kitName);
}
public bool TryGetKit(string kitName, out KitViewModel? kit)
{
kit = _config.Kits.FirstOrDefault(b => b.Name == kitName);
return kit is not null;
}
public void GiveKit(MyPlayer player, MyInventoryBase inventory, KitViewModel kit)
{
if (kit.UseCooldownMinutes > 0)
{
CheckTable();
_db.Insert(new PlayerCooldown {Id = player.Id.SteamId.ToString(), KitName = kit.Name, LastUsed = DateTime.Now});
}
MyBankingSystem.ChangeBalance(player.Identity.IdentityId, kit.UseCost);
foreach (var item in kit.Items.Where(b => b.Probability >= 1 || b.Probability < MyRandom.Instance.GetRandomFloat(0, 1)))
{
inventory.AddItems((MyFixedPoint)item.Amount, MyObjectBuilderSerializer.CreateNewObject(item.Id));
}
Log.Info($"Given kit {kit.Name} to {player.DisplayName} ({player.Id.SteamId})");
}
public bool CanGiveKit(MyPlayer player, KitViewModel kit, out string reason)
{
reason = string.Empty;
var level = MySession.Static.GetUserPromoteLevel(player.Id.SteamId);
if (level < kit.RequiredPromoteLevel ||
!string.IsNullOrEmpty(kit.LpPermission))
{
var api = LuckPermsProvider.get();
var torchPlayer = _multiplayerManager.Players[player.Id.SteamId];
if (!api.getPlayerAdapter(typeof(IPlayer)).getPermissionData(torchPlayer).checkPermission(kit.LpPermission).asBoolean())
{
reason = "Not enough rights to acquire this";
return false;
}
}
if (kit.UseCost > 0 && kit.UseCost > MyBankingSystem.GetBalance(player.Identity.IdentityId))
{
reason = "Not enough money to acquire this";
return false;
}
if (kit.UseCooldownMinutes <= 0)
return true;
var sql = Sql.Builder.Where("id = @0", player.Id.SteamId).Append("AND kit_name = @0", kit.Name);
CheckTable();
var playerCooldown = _db.SingleOrDefault<PlayerCooldown>(sql);
var cooldown = DateTime.Now - playerCooldown?.LastUsed;
if (cooldown is null)
return true;
if (cooldown > TimeSpan.FromMinutes(kit.UseCooldownMinutes))
{
_db.Delete<PlayerCooldown>(sql);
return true;
}
reason = $"Next use available in {TimeSpan.FromMinutes(kit.UseCooldownMinutes) - cooldown:dd\\.hh\\:mm\\:ss}";
return false;
}
public bool CanGiveRespawnKit(MyPlayer player, KitViewModel kit, string respawnName, out string reason)
{
reason = "Invalid respawn name";
return kit.RespawnPodWildcards.Any(respawnName.Glob) && CanGiveKit(player, kit, out reason);
}
private void CheckTable()
{
_db.Execute("create table if not exists cooldown (uid INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL, kit_name TEXT NOT NULL, last_used DATETIME NOT NULL)");
}
}
[TableName("cooldown")]
[PrimaryKey(nameof(Uid), AutoIncrement = true)]
public class PlayerCooldown
{
[Column]
public long Uid { get; set; }
[Column]
public string Id { get; set; } = string.Empty;
[Column]
public string KitName { get; set; } = string.Empty;
[Column]
public DateTime LastUsed { get; set; }
}
public interface IKitManager : IManager
{
void GiveKit(MyPlayer player, MyInventoryBase inventory, string kitName);
void GiveKit(MyPlayer player, MyInventoryBase inventory, KitViewModel kit);
bool CanGiveKit(MyPlayer player, string kitName, out string reason);
bool CanGiveKit(MyPlayer player, KitViewModel kit, out string reason);
bool CanGiveRespawnKit(MyPlayer player, KitViewModel kit, string respawnName, out string reason);
bool CanGiveRespawnKit(MyPlayer player, string kitName, string respawnName, out string reason);
KitViewModel GetKit(string kitName);
bool TryGetKit(string kitName, out KitViewModel? kit);
}

62
Kits/KitViewModel.cs Normal file
View File

@@ -0,0 +1,62 @@
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using Kits.Views;
using Torch;
using Torch.Views;
using VRage.Game.ModAPI;
using VRage.ObjectBuilders;
namespace Kits;
public class KitViewModel : ViewModel
{
[Display(Name = "Name", GroupName = "General")]
[XmlAttribute]
public string Name { get; set; } = "unnamed";
[Display(Name = "Cost", GroupName = "Usage", Description = "Credits cost to use this kit")]
public long UseCost { get; set; } = 0;
[Display(Name = "Cooldown Minutes", GroupName = "Usage", Description = "Cooldown to use this kit per player in minutes")]
public ulong UseCooldownMinutes { get; set; } = 0;
[Display(Name = "Required Promote Level", GroupName = "Conditions", Description = "Minimal Promote Level to use this kit")]
public MyPromoteLevel RequiredPromoteLevel { get; set; } = MyPromoteLevel.None;
[Display(Name = "Lp Permission", GroupName = "Conditions", Description = "Luck Perms permission to use this kit (leave empty to disable, example: kits.vip)")]
public string LpPermission { get; set; } = "";
[Display(Name = "Respawn Pod Wildcards", GroupName = "Usage", Description = "Respawn pod name wildcard to filter usage of kit, leave empty to disable")]
public ObservableCollection<string> RespawnPodWildcards { get; set; } = new();
[Display(Name = "Items", GroupName = "General", EditorType = typeof(EditButton))]
[XmlArrayItem("Item")]
public ObservableCollection<KitItemViewModel> Items { get; set; } = new();
public override string ToString()
{
return Name;
}
}
public class KitItemViewModel : ViewModel
{
[Display(Name = "Id", EditorType = typeof(DefinitionIdEditor), Description = "TypeId/SubtypeId. Only items are allowed. for e.g Component/SteelPlate, Ore/Stone, PhysicalGunObject/RapidFireAutomaticRifleItem")]
public DefinitionId Id { get; set; } = new();
[Display(Name = "Probability", Description = "Probability of the item. 1 is 100%, 0 is 0%")]
public float Probability { get; set; } = 1;
[Display(Name = "Amount")]
public float Amount { get; set; } = 0;
[XmlIgnore]
public string Name => Id.ToString();
public override string ToString()
{
return Id.ToString();
}
}
public class DefinitionId : ViewModel
{
[XmlAttribute]
public string TypeId { get; set; } = "type";
[XmlAttribute]
public string SubtypeId { get; set; } = "subtype";
public override string ToString() => $"{TypeId}/{SubtypeId}";
public static implicit operator SerializableDefinitionId(DefinitionId id) => new(MyObjectBuilderType.ParseBackwardsCompatible(id.TypeId), id.SubtypeId);
}

47
Kits/Kits.csproj Normal file
View File

@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>10</LangVersion>
<UseWpf>true</UseWpf>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LuckPerms.Torch.Api" Version="5.4.0" />
<PackageReference Include="PetaPoco.Compiled" Version="6.0.480" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="Torch.Server.ReferenceAssemblies" Version="1.3.1.207-master" PrivateAssets="all" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup>
<None Update="manifest.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Remove="schema.xsd" />
<EmbeddedResource Include="schema.xsd" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\EditButton.cs">
<DependentUpon>EditButton.xaml</DependentUpon>
</Compile>
<Compile Update="Views\ProperCollectionEditor.cs">
<DependentUpon>ProperCollectionEditor.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\heh\heh.csproj" />
</ItemGroup>
</Project>

50
Kits/Plugin.cs Normal file
View File

@@ -0,0 +1,50 @@
using System.IO;
using System.Windows.Controls;
using heh;
using heh.Utils;
using Kits.Views;
using Torch;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.API.Session;
using Torch.Views;
namespace Kits;
public class Plugin : TorchPluginBase, IWpfPlugin
{
private ProperPersistent<Config> _config = null!;
public override void Init(ITorchBase torch)
{
base.Init(torch);
CheckConfigSchema();
_config = new(Path.Combine(StoragePath, "Kits.xml"));
Torch.Managers.AddManager(DbManager.Static);
Torch.Managers.GetManager<ITorchSessionManager>().AddFactory(s => new KitManager(s.Torch, _config.Data));
}
private void CheckConfigSchema()
{
var files = Directory.EnumerateFiles(StoragePath, "Kits.*.xsd").ToList();
if (files.Any() && files[0].Substring(files[0].IndexOf('.') + 1, Manifest.Version.Length) == Manifest.Version)
return;
foreach (var file in files)
{
File.Delete(file);
}
using var resource = typeof(Plugin).Assembly.GetManifestResourceStream("Kits.schema.xsd");
using var stream = File.Create(Path.Combine(StoragePath, $"Kits.{Manifest.Version}.xsd"));
resource?.CopyTo(stream);
}
public UserControl GetControl() => new PropertyGrid
{
Margin = new(3),
DataContext = _config.Data
};
}

View File

@@ -0,0 +1,18 @@
<UserControl x:Class="Kits.Views.DefinitionIdEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:kits="clr-namespace:Kits"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<kits:DefinitionId/>
</UserControl.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding TypeId}" Margin="0,0,3,0" />
<TextBlock Text="/" Margin="0,0,3,0" />
<TextBox Text="{Binding SubtypeId}" Margin="0,0,3,0" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Kits.Views;
public partial class DefinitionIdEditor : UserControl
{
public DefinitionIdEditor()
{
InitializeComponent();
}
}

20
Kits/Views/EditButton.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.Windows;
using System.Windows.Controls;
namespace Kits.Views;
public partial class EditButton : UserControl
{
public EditButton()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
new ProperCollectionEditor
{
DataContext = DataContext,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = Window.GetWindow(this)
}.ShowDialog();
}
}

View File

@@ -0,0 +1,9 @@
<UserControl x:Class="Kits.Views.EditButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Button Content="Edit" Click="ButtonBase_OnClick" />
</UserControl>

View File

@@ -0,0 +1,21 @@
using System.Collections;
using System.Windows;
namespace Kits.Views;
public partial class ProperCollectionEditor : Window
{
public ProperCollectionEditor()
{
InitializeComponent();
}
private void ButtonAdd_OnClick(object sender, RoutedEventArgs e)
{
((IList)DataContext).Add(Activator.CreateInstance(DataContext.GetType().GenericTypeArguments[0]));
}
private void ButtonDelete_OnClick(object sender, RoutedEventArgs e)
{
if (ElementsGrid.SelectedItem is { } item)
((IList)DataContext).Remove(item);
}
}

View File

@@ -0,0 +1,35 @@
<Window x:Class="Kits.Views.ProperCollectionEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:views="clr-namespace:Torch.Views;assembly=Torch"
mc:Ignorable="d"
Title="Proper Collection Editor" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<DataGrid Margin="3" Name="ElementsGrid" AutoGenerateColumns="False" ItemsSource="{Binding }" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Margin="5" Content="Add" Click="ButtonAdd_OnClick" />
<Button Grid.Column="1" Margin="5" Content="Delete" Click="ButtonDelete_OnClick" />
</Grid>
</Grid>
<views:PropertyGrid Grid.Column="1" DataContext="{Binding ElementName=ElementsGrid, Path=SelectedItem, Mode=OneWay}" Margin="3" />
</Grid>
</Window>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>LuckPerms.Loader</Name> <Name>Kits</Name>
<Guid>7E4B3CC8-64FA-416E-8910-AACDF2DA5E2C</Guid> <Guid>d095391d-b5ec-43a9-8ba4-6c4909227e6e</Guid>
<Version>v5.4.106.3</Version> <Version>v1.0.6</Version>
</PluginManifest> </PluginManifest>

73
Kits/schema.xsd Normal file
View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Config" nillable="true" type="Config" />
<xs:complexType name="Config">
<xs:complexContent mixed="false">
<xs:extension base="ViewModel">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="Kits" type="ArrayOfKitViewModel" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ViewModel" abstract="true" />
<xs:complexType name="DefinitionId">
<xs:complexContent mixed="false">
<xs:extension base="ViewModel">
<xs:attribute name="TypeId" type="xs:string" />
<xs:attribute name="SubtypeId" type="xs:string" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="KitItemViewModel">
<xs:complexContent mixed="false">
<xs:extension base="ViewModel">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="Id" type="DefinitionId" />
<xs:element minOccurs="1" maxOccurs="1" name="Probability" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="Amount" type="xs:float" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="KitViewModel">
<xs:complexContent mixed="false">
<xs:extension base="ViewModel">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="UseCost" type="xs:long" />
<xs:element minOccurs="0" maxOccurs="1" name="UseCooldownMinutes" type="xs:unsignedLong" />
<xs:element minOccurs="0" maxOccurs="1" name="RequiredPromoteLevel" type="MyPromoteLevel" />
<xs:element minOccurs="0" maxOccurs="1" name="LpPermission" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="RespawnPodWildcards" type="ArrayOfString" />
<xs:element minOccurs="1" maxOccurs="1" name="Items" type="ArrayOfKitItemViewModel" />
</xs:sequence>
<xs:attribute name="Name" type="xs:string" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="MyPromoteLevel">
<xs:restriction base="xs:string">
<xs:enumeration value="None" />
<xs:enumeration value="Scripter" />
<xs:enumeration value="Moderator" />
<xs:enumeration value="SpaceMaster" />
<xs:enumeration value="Admin" />
<xs:enumeration value="Owner" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ArrayOfString">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="string" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArrayOfKitItemViewModel">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Item" nillable="true" type="KitItemViewModel" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArrayOfKitViewModel">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Kit" nillable="true" type="KitViewModel" />
</xs:sequence>
</xs:complexType>
</xs:schema>

View File

@@ -14,7 +14,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="heh" Version="1.0.12" />
<PackageReference Include="Torch.Server.ReferenceAssemblies" Version="1.3.1.207-master" PrivateAssets="all" IncludeAssets="compile" /> <PackageReference Include="Torch.Server.ReferenceAssemblies" Version="1.3.1.207-master" PrivateAssets="all" IncludeAssets="compile" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.0" PrivateAssets="all" /> <PackageReference Include="PropertyChanged.Fody" Version="4.0.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
@@ -25,4 +24,8 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\heh\heh.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -8,56 +8,39 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<PluginBasePath>$(MSBuildThisFileDirectory)$(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\</PluginBasePath>
<PluginZipPath>$(PluginBasePath)plugin.zip</PluginZipPath>
<PluginZipHashPath>$(PluginBasePath)plugin.zip.sha256</PluginZipHashPath>
<PluginNamePath>$(PluginBasePath)name.txt</PluginNamePath>
<PluginManifestPath>$(PluginBasePath)manifest.xml</PluginManifestPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="$(PluginZipPath)" LogicalName="plugin.zip" />
<EmbeddedResource Include="$(PluginZipHashPath)" LogicalName="plugin.zip.sha256" />
<EmbeddedResource Include="$(PluginNamePath)" LogicalName="name.txt" />
<Content Include="$(PluginManifestPath)" Link="manifest.xml" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Krafs.Publicizer" Version="2.2.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PolySharp" Version="1.13.2"> <PackageReference Include="PolySharp" Version="1.13.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="torch.server.referenceassemblies" Version="1.3.1.260-master" PrivateAssets="all" IncludeAssets="compile" /> <PackageReference Include="torch.server.referenceassemblies" Version="1.3.1.260-master" PrivateAssets="all" IncludeAssets="compile" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Publicize Include="Torch:Torch.TorchBase.RegisterAuxAssembly" />
<Publicize Include="Torch:Torch.Managers.PluginManager._plugins" />
<Publicize Include="Torch:Torch.TorchPluginBase.Manifest" />
<Publicize Include="Torch:Torch.TorchPluginBase.StoragePath" />
</ItemGroup>
<ItemGroup>
<Content Include="manifest.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LuckPerms.Torch\LuckPerms.Torch.csproj" ReferenceOutputAssembly="false" Private="false" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System.IO.Compression" /> <Reference Include="System.IO.Compression" />
</ItemGroup> </ItemGroup>
<Target Name="BuildArchive" BeforeTargets="PreBuildEvent">
<PropertyGroup>
<PluginDir>..\LuckPerms.Torch\bin\$(Configuration)\$(TargetFramework)\win-x64\</PluginDir>
<PluginZipPath>$(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\plugin.zip</PluginZipPath>
</PropertyGroup>
<ZipDirectory DestinationFile="$(PluginZipPath)" SourceDirectory="$(PluginDir)" Overwrite="true" />
<ItemGroup>
<EmbeddedResource Include="$(PluginZipPath)" LogicalName="plugin.zip" />
</ItemGroup>
</Target>
</Project> </Project>

View File

@@ -6,67 +6,105 @@ using Torch;
using Torch.API; using Torch.API;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.API.Plugins; using Torch.API.Plugins;
using Torch.Collections;
using Torch.Managers; using Torch.Managers;
using Torch.Utils;
namespace LuckPerms.Loader; namespace LuckPerms.Loader;
public class Plugin : TorchPluginBase public class Plugin : TorchPluginBase
{ {
private static readonly ITorchPlugin MainPluginInstance; private static readonly ITorchPlugin MainPluginInstance;
private static readonly ILogger Log = LogManager.GetLogger("LuckPerms.Loader"); private static readonly ILogger Log = LogManager.GetLogger("Loader");
static Plugin() static Plugin()
{ {
string assemblyName;
using (var infoStream = typeof(Plugin).Assembly.GetManifestResourceStream("name.txt")!)
using (var infoStreamReader = new StreamReader(infoStream))
assemblyName = infoStreamReader.ReadLine()!.Trim();
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var torch = (ITorchServer)TorchBase.Instance; var torch = (ITorchServer)TorchBase.Instance;
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
var dir = new DirectoryInfo(Path.Combine(torch.InstancePath, "cache", "luckperms.loader")); var dir = new DirectoryInfo(Path.Combine(torch.InstancePath, "cache", assemblyName));
void ExtractCache()
{
using var currentHashStream = typeof(Plugin).Assembly.GetManifestResourceStream("plugin.zip.sha256")!;
var currentHash = currentHashStream.ReadToEnd();
var hashPath = Path.Combine(dir.FullName, "plugin.zip.sha256");
if (dir.Exists)
{
if (File.Exists(hashPath))
{
Log.Info("Checking cache");
if (dir.Exists) var hash = File.ReadAllBytes(hashPath);
dir.Delete(true);
if (hash.SequenceEqual(currentHash)) return;
}
dir.Delete(true);
}
Log.Info($"Extracting cache to {dir}"); Log.Info($"Extracting cache to {dir}");
using (var pluginStream = typeof(Plugin).Assembly.GetManifestResourceStream("plugin.zip")!) using var pluginStream = typeof(Plugin).Assembly.GetManifestResourceStream("plugin.zip")!;
using (var archive = new ZipArchive(pluginStream, ZipArchiveMode.Read)) using var archive = new ZipArchive(pluginStream, ZipArchiveMode.Read);
archive.ExtractToDirectory(dir.FullName); archive.ExtractToDirectory(dir.FullName);
File.WriteAllBytes(hashPath, currentHash);
}
Log.Info("Injecting LuckPerms"); ExtractCache();
Log.Info($"Injecting {assemblyName}");
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
{ {
var fileName = args.Name[..args.Name.IndexOf(',')]; var fileName = args.Name[..args.Name.IndexOf(',')];
if (AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(b =>
b.GetName().Name?.Equals(fileName, StringComparison.OrdinalIgnoreCase) is true) is { } assembly)
return assembly;
var path = Path.Combine(dir.FullName, fileName + ".dll"); var path = Path.Combine(dir.FullName, fileName + ".dll");
return File.Exists(path) ? Assembly.LoadFile(path) : null; return File.Exists(path) ? Assembly.LoadFile(path) : null;
}; };
var mainAssembly = Assembly.LoadFile(Path.Combine(dir.FullName, "LuckPerms.Torch.dll")); var mainAssembly = Assembly.LoadFile(Path.Combine(dir.FullName, $"{assemblyName}.dll"));
var pluginType = mainAssembly.GetType("LuckPerms.Torch.Plugin", true)!; var pluginType = mainAssembly.GetType($"{assemblyName}.Plugin", true)!;
// a hacky way to configure JVM // a hacky way to configure the plugin
RuntimeHelpers.RunClassConstructor(pluginType.TypeHandle); RuntimeHelpers.RunClassConstructor(pluginType.TypeHandle);
TorchBase.RegisterAuxAssembly(mainAssembly); typeof(TorchBase).GetMethod("RegisterAuxAssembly", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { mainAssembly });
MainPluginInstance = (ITorchPlugin)Activator.CreateInstance(pluginType)!; MainPluginInstance = (ITorchPlugin)Activator.CreateInstance(pluginType)!;
if (MainPluginInstance is not TorchPluginBase pluginBase) return;
pluginBase.Manifest = PluginManifest.Load(Path.Combine(dir.FullName, "manifest.xml"));
pluginBase.StoragePath = torch.InstancePath;
} }
public override void Init(ITorchBase torch) public override void Init(ITorchBase torch)
{ {
if (MainPluginInstance is TorchPluginBase pluginBase)
{
typeof(TorchPluginBase).GetProperty(nameof(Manifest))!.SetValue(pluginBase, Manifest);
typeof(TorchPluginBase).GetProperty(nameof(StoragePath))!.SetValue(pluginBase, StoragePath);
}
var pluginManager = torch.Managers.GetManager<PluginManager>(); var pluginManager = torch.Managers.GetManager<PluginManager>();
var plugins =
(MtObservableSortedDictionary<Guid, ITorchPlugin>)typeof(PluginManager).GetField("_plugins",
BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(pluginManager);
plugins.Remove(Manifest.Guid);
plugins.Add(Manifest.Guid, MainPluginInstance);
pluginManager._plugins.Remove(Manifest.Guid);
pluginManager._plugins.Add(Manifest.Guid, MainPluginInstance);
MainPluginInstance.Init(torch); MainPluginInstance.Init(torch);
Log.Info("Injected successfully"); Log.Info("Injected successfully");
} }
} }

View File

@@ -2,12 +2,6 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
".NETFramework,Version=v4.8": { ".NETFramework,Version=v4.8": {
"Krafs.Publicizer": {
"type": "Direct",
"requested": "[2.2.1, )",
"resolved": "2.2.1",
"contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q=="
},
"Microsoft.NETFramework.ReferenceAssemblies": { "Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.3, )", "requested": "[1.0.3, )",
@@ -62,6 +56,7 @@
"protobuf-net": "1.0.0" "protobuf-net": "1.0.0"
} }
} }
} },
".NETFramework,Version=v4.8/win-x64": {}
} }
} }

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>LuckPerms.Torch.Api</id>
<version>5.4</version>
<authors>LuckPerms.Torch.Api</authors>
<description>Package Description</description>
<dependencies>
<group targetFramework=".NETFramework4.8">
<dependency id="Torch.Loader" version="1.0.0" />
<dependency id="IKVM.Maven.Sdk" version="1.6.1" />
</group>
</dependencies>
</metadata>
</package>

View File

@@ -0,0 +1,5 @@
<Project>
<ItemGroup>
<MavenReference Include="net.luckperms:api" Version="5.4" />
</ItemGroup>
</Project>

View File

@@ -52,7 +52,7 @@ public class LpTorchBootstrap : LuckPermsBootstrap
public Collection getPlayerList() => Sync.Players?.GetAllPlayers() public Collection getPlayerList() => Sync.Players?.GetAllPlayers()
.Select(b => Sync.Players.TryGetPlayerIdentity(b)?.DisplayName).Where(b => b is not null).ToCollection() ?? Collections.EMPTY_LIST; .Select(b => Sync.Players.TryGetPlayerIdentity(b)?.DisplayName).Where(b => b is not null).ToCollection() ?? Collections.EMPTY_LIST;
public Platform.Type getType() => Platform.Type.STANDALONE; // meh public Platform.Type getType() => Platform.Type.BUKKIT; // meh
public SchedulerAdapter getScheduler() => _schedulerAdapter ??= new LpSchedulerAdapter(this, _torch); public SchedulerAdapter getScheduler() => _schedulerAdapter ??= new LpSchedulerAdapter(this, _torch);

View File

@@ -41,12 +41,12 @@ public class LpTorchPlugin(LuckPermsBootstrap bootstrap, ITorchBase torch) : Abs
private LpContextManager? _contextManager; private LpContextManager? _contextManager;
private LpSenderFactory? _senderFactory; private LpSenderFactory? _senderFactory;
private LpConnectionListener? _connectionListener; private LpConnectionListener? _connectionListener;
private global::Torch.Managers.DependencyManager? _dependencyManager;
public override LuckPermsBootstrap getBootstrap() => bootstrap; public override LuckPermsBootstrap getBootstrap() => bootstrap;
protected override void setupSenderFactory() protected override void setupSenderFactory()
{ {
_senderFactory = new LpSenderFactory(this); _senderFactory = new(this);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _senderFactory);
} }
public override Sender getConsoleSender() => _senderFactory?.wrap(torch) ?? throw new InvalidOperationException("call setupSenderFactory first"); public override Sender getConsoleSender() => _senderFactory?.wrap(torch) ?? throw new InvalidOperationException("call setupSenderFactory first");
@@ -61,8 +61,7 @@ public class LpTorchPlugin(LuckPermsBootstrap bootstrap, ITorchBase torch) : Abs
protected override void registerCommands() protected override void registerCommands()
{ {
_commandManager = new LpCommandManager(this, _senderFactory!); _commandManager = new(this, _senderFactory!);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _commandManager);
} }
protected override void setupManagers() protected override void setupManagers()
@@ -71,16 +70,29 @@ public class LpTorchPlugin(LuckPermsBootstrap bootstrap, ITorchBase torch) : Abs
_groupManager = new(this); _groupManager = new(this);
_trackManager = new(this); _trackManager = new(this);
_connectionListener = new(this); _connectionListener = new(this);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _connectionListener); if (torch.CurrentSession is null)
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => new ModApiManager()); {
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _senderFactory);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _commandManager);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _connectionListener);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => new ModApiManager());
}
else
{
_dependencyManager = new(torch.CurrentSession.Managers);
_dependencyManager.AddManager(_senderFactory);
_dependencyManager.AddManager(_commandManager);
_dependencyManager.AddManager(_connectionListener);
_dependencyManager.AddManager(new ModApiManager());
}
} }
protected override CalculatorFactory provideCalculatorFactory() => new LpCalculatorFactory(this); protected override CalculatorFactory provideCalculatorFactory() => new LpCalculatorFactory(this);
protected override void setupContextManager() protected override void setupContextManager()
{ {
_contextManager = new LpContextManager(this); _contextManager = new(this);
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _contextManager);
} }
public override GroupManager getGroupManager() => _groupManager ?? throw new InvalidOperationException("call setupManagers first"); public override GroupManager getGroupManager() => _groupManager ?? throw new InvalidOperationException("call setupManagers first");
@@ -123,6 +135,23 @@ public class LpTorchPlugin(LuckPermsBootstrap bootstrap, ITorchBase torch) : Abs
CommandPrefixPatch.Patch(context); CommandPrefixPatch.Patch(context);
patchManager.Commit(); patchManager.Commit();
if (_dependencyManager is null)
{
torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => _contextManager);
}
else
{
_dependencyManager.AddManager(_contextManager);
_dependencyManager.Attach();
}
}
protected override void removePlatformHooks()
{
base.removePlatformHooks();
_dependencyManager?.Detach();
} }
protected override AbstractEventBus provideEventBus(LuckPermsApiProvider luckPermsApiProvider) => new LpEventBus(this, luckPermsApiProvider, torch); protected override AbstractEventBus provideEventBus(LuckPermsApiProvider luckPermsApiProvider) => new LpEventBus(this, luckPermsApiProvider, torch);

View File

@@ -24,6 +24,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="torch.server.referenceassemblies" Version="1.3.1.260-master" PrivateAssets="all" IncludeAssets="compile" /> <PackageReference Include="torch.server.referenceassemblies" Version="1.3.1.260-master" PrivateAssets="all" IncludeAssets="compile" />
<PackageReference Include="Torch.Loader" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
@@ -100,9 +101,15 @@
<MySqlPath>$(LibsPath)mysql-connector-j-8.0.33.jar</MySqlPath> <MySqlPath>$(LibsPath)mysql-connector-j-8.0.33.jar</MySqlPath>
<ProtobufPath>$(LibsPath)protobuf-java-3.21.9.jar</ProtobufPath> <ProtobufPath>$(LibsPath)protobuf-java-3.21.9.jar</ProtobufPath>
<SqlitePath>$(LibsPath)sqlite-jdbc-3.43.2.2.jar</SqlitePath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<IkvmReference Include="$(SqlitePath)">
<References>$(Slf4jApiPath)</References>
</IkvmReference>
<IkvmReference Include="$(MySqlPath)"> <IkvmReference Include="$(MySqlPath)">
<References>$(ProtobufPath)</References> <References>$(ProtobufPath)</References>
</IkvmReference> </IkvmReference>
@@ -122,7 +129,7 @@
</IkvmReference> </IkvmReference>
<IkvmReference Include="$(JnaPath)" /> <IkvmReference Include="$(JnaPath)" />
<IkvmReference Include="$(H2Path)" /> <!--<IkvmReference Include="$(H2Path)" />-->
<IkvmReference Include="$(PostgreSqlPath)" /> <IkvmReference Include="$(PostgreSqlPath)" />
<IkvmReference Include="$(MongoDriverLegacyPath)"> <IkvmReference Include="$(MongoDriverLegacyPath)">
@@ -234,21 +241,17 @@
<IkvmReference Include="$(SnakeYamlPath)" /> <IkvmReference Include="$(SnakeYamlPath)" />
<IkvmReference Include="$(CheckerQualPath)" /> <IkvmReference Include="$(CheckerQualPath)" />
<IkvmReference Include="$(ApiJarPath)" /> <IkvmReference Include="$(ApiJarPath)">
<AssemblyName>api</AssemblyName>
<DisableAutoAssemblyName>true</DisableAutoAssemblyName>
</IkvmReference>
<IkvmReference Include="$(CommonJarPath)"> <IkvmReference Include="$(CommonJarPath)">
<References>$(ApiJarPath);$(ConfigurateCorePath);$(ConfigurateGsonPath);$(ConfigurateHoconPath);$(ConfigurateYamlPath);$(SnakeYamlPath);$(CheckerQualPath);$(AdventureApiPath);$(AdventureKeyPath);$(AdventureTextPlainPath);$(AdventureTextLegacyPath);$(AdventureTextMinimessagePath);$(ExaminationApiPath);$(GuavaPath);$(GsonPath);$(ConfigurateTomlPath);$(BrigadierPath);$(EventApiPath);$(Slf4jApiPath);$(Log4jApiPath);$(CaffeinePath);$(OkHttpPath);$(OkioPath);$(ByteBuddyPath);$(JnatsPath);$(HikariCPPath);$(JedisPath);$(CommonsPoolPath);$(AmqpClientPath);$(BsonPath);$(MongoDriverCorePath);$(MongoDriverLegacyPath);$(MongoDriverSyncPath);$(PostgreSqlPath);$(H2Path);$(MariaDbPath);$(MySqlPath)</References> <References>$(ApiJarPath);$(ConfigurateCorePath);$(ConfigurateGsonPath);$(ConfigurateHoconPath);$(ConfigurateYamlPath);$(SnakeYamlPath);$(CheckerQualPath);$(AdventureApiPath);$(AdventureKeyPath);$(AdventureTextPlainPath);$(AdventureTextLegacyPath);$(AdventureTextMinimessagePath);$(ExaminationApiPath);$(GuavaPath);$(GsonPath);$(ConfigurateTomlPath);$(BrigadierPath);$(EventApiPath);$(Slf4jApiPath);$(Log4jApiPath);$(CaffeinePath);$(OkHttpPath);$(OkioPath);$(ByteBuddyPath);$(JnatsPath);$(HikariCPPath);$(JedisPath);$(CommonsPoolPath);$(AmqpClientPath);$(BsonPath);$(MongoDriverCorePath);$(MongoDriverLegacyPath);$(MongoDriverSyncPath);$(PostgreSqlPath);$(MariaDbPath);$(MySqlPath);$(SqlitePath)</References>
</IkvmReference> </IkvmReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="manifest.xml"> <!--<EmbeddedResource Include="Resources\schema\h2.sql" LogicalName="me.lucko.luckperms.schema.h2.sql" />-->
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\schema\h2.sql" LogicalName="me.lucko.luckperms.schema.h2.sql" />
<EmbeddedResource Include="Resources\schema\mariadb.sql" LogicalName="me.lucko.luckperms.schema.mariadb.sql" /> <EmbeddedResource Include="Resources\schema\mariadb.sql" LogicalName="me.lucko.luckperms.schema.mariadb.sql" />
<EmbeddedResource Include="Resources\schema\mysql.sql" LogicalName="me.lucko.luckperms.schema.mysql.sql" /> <EmbeddedResource Include="Resources\schema\mysql.sql" LogicalName="me.lucko.luckperms.schema.mysql.sql" />
<EmbeddedResource Include="Resources\schema\postgresql.sql" LogicalName="me.lucko.luckperms.schema.postgresql.sql" /> <EmbeddedResource Include="Resources\schema\postgresql.sql" LogicalName="me.lucko.luckperms.schema.postgresql.sql" />

View File

@@ -21,7 +21,8 @@ public class ModApiManager : IManager
MyScriptCompiler.Static.AddReferencedAssemblies( MyScriptCompiler.Static.AddReferencedAssemblies(
typeof(net.luckperms.api.LuckPerms).Assembly.Location, // net.luckperms.api.dll typeof(net.luckperms.api.LuckPerms).Assembly.Location, // net.luckperms.api.dll
typeof(java.lang.Boolean).Assembly.Location, // IKVM.Java.dll typeof(java.lang.Boolean).Assembly.Location, // IKVM.Java.dll
typeof(ModApiManager).Assembly.Location // LuckPerms.Torch.dll typeof(ModApiManager).Assembly.Location, // LuckPerms.Torch.dll
typeof(ITorchBase).Assembly.Location // Torch.API.dll
); );
using var whitelist = MyScriptCompiler.Static.Whitelist.OpenBatch(); using var whitelist = MyScriptCompiler.Static.Whitelist.OpenBatch();

View File

@@ -0,0 +1,61 @@
using System.IO;
using java.lang;
using LuckPerms.Torch.Impl;
using NLog;
using Torch;
using Torch.API;
using Torch.API.Managers;
using Exception = System.Exception;
namespace LuckPerms.Torch.PlatformApi;
public class LuckPermsPlatformManager : IManager
{
private readonly ILogger _log = LogManager.GetCurrentClassLogger();
private readonly LpTorchBootstrap _bootstrap;
public LuckPermsPlatformManager(TorchPluginBase plugin, ITorchServer server, ILogger log)
{
_bootstrap = new(server, plugin, log, Path.Combine(plugin.StoragePath, "luckperms"));
try
{
log.Info("Initializing LuckPerms");
_bootstrap.Plugin.load();
}
catch (Exception e)
{
log.Fatal(e);
throw;
}
finally
{
_bootstrap.LoadLatch.countDown();
}
}
public void Attach()
{
try
{
_log.Info("Loading LuckPerms");
Thread.currentThread().setContextClassLoader(LpDependencyManager.CurrentClassLoader);
_bootstrap.Plugin.enable();
}
catch (Exception e)
{
_log.Fatal(e);
throw;
}
finally
{
_bootstrap.EnableLatch.countDown();
}
}
public void Detach()
{
_log.Info("Unloading LuckPerms");
_bootstrap.Plugin.disable();
}
}

View File

@@ -3,10 +3,13 @@ using System.IO;
using java.lang; using java.lang;
using java.util; using java.util;
using LuckPerms.Torch.Impl; using LuckPerms.Torch.Impl;
using LuckPerms.Torch.PlatformApi;
using NLog; using NLog;
using Sandbox; using Sandbox;
using Torch; using Torch;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Exception = System.Exception; using Exception = System.Exception;
using Object = java.lang.Object; using Object = java.lang.Object;
@@ -23,58 +26,14 @@ public class Plugin : TorchPluginBase
} }
public static readonly ILogger Log = LogManager.GetLogger("LuckPerms"); public static readonly ILogger Log = LogManager.GetLogger("LuckPerms");
private LpTorchBootstrap? _bootstrap;
public override void Init(ITorchBase torch) public override void Init(ITorchBase torch)
{ {
base.Init(torch); base.Init(torch);
Torch.GameStateChanged += TorchOnGameStateChanged;
_bootstrap = new((ITorchServer)Torch, this, Log, Path.Combine(StoragePath, "luckperms"));
try
{
Log.Info("Initializing LuckPerms");
_bootstrap.Plugin.load();
}
catch (Exception e)
{
Log.Fatal(e);
throw;
}
finally
{
_bootstrap.LoadLatch.countDown();
}
}
private void TorchOnGameStateChanged(MySandboxGame game, TorchGameState newState) var platformManager = new LuckPermsPlatformManager(this, (ITorchServer)torch, Log);
{
if (_bootstrap is null) Torch.Managers.GetManager<ITorchSessionManager>().AddFactory(_ => platformManager);
throw new InvalidOperationException("Plugin is not initialized");
switch (newState)
{
case TorchGameState.Loading:
try
{
Log.Info("Loading LuckPerms");
Thread.currentThread().setContextClassLoader(LpDependencyManager.CurrentClassLoader);
_bootstrap.Plugin.enable();
}
catch (Exception e)
{
Log.Fatal(e);
throw;
}
finally
{
_bootstrap.EnableLatch.countDown();
}
break;
case TorchGameState.Unloading:
Log.Info("Unloading LuckPerms");
_bootstrap.Plugin.disable();
break;
}
} }
} }

View File

@@ -58,7 +58,6 @@ server: global
# |=> MongoDB # |=> MongoDB
# #
# | Flatfile/local database - don't require any extra configuration # | Flatfile/local database - don't require any extra configuration
# |=> H2 (preferred over SQLite)
# |=> SQLite # |=> SQLite
# #
# | Readable & editable text files - don't require any extra configuration # | Readable & editable text files - don't require any extra configuration
@@ -71,9 +70,9 @@ server: global
# | and all stored in the same file by switching to a combined storage variant. # | and all stored in the same file by switching to a combined storage variant.
# | Just add '-combined' to the end of the storage-method, e.g. 'yaml-combined' # | Just add '-combined' to the end of the storage-method, e.g. 'yaml-combined'
# #
# - A H2 database is the default option. # - A SQLite database is the default option.
# - If you want to edit data manually in "traditional" storage files, we suggest using YAML. # - If you want to edit data manually in "traditional" storage files, we suggest using YAML.
storage-method: h2 storage-method: sqlite
# The following block defines the settings for remote database storage methods. # The following block defines the settings for remote database storage methods.
# #

Binary file not shown.

View File

@@ -45,6 +45,12 @@
"resolved": "1.13.2", "resolved": "1.13.2",
"contentHash": "XwNhfkr7IeUiH8AE4pzob8YioxfL6nxgAx+fHEeWCObY/NZuBMfWLh39FznXbneKvagiqeeI7quIvZ6P1eVaEA==" "contentHash": "XwNhfkr7IeUiH8AE4pzob8YioxfL6nxgAx+fHEeWCObY/NZuBMfWLh39FznXbneKvagiqeeI7quIvZ6P1eVaEA=="
}, },
"Torch.Loader": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "GAf9Mv1t1/qTGHSgDqkiKAc7Xbh36+U8Ce1PuSoJZNKxHVmzbKHc3nSVz0dIBHhLE7Op8k60NfmclDRAQAppbQ=="
},
"Torch.Server.ReferenceAssemblies": { "Torch.Server.ReferenceAssemblies": {
"type": "Direct", "type": "Direct",
"requested": "[1.3.1.260-master, )", "requested": "[1.3.1.260-master, )",

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.Build.NoTargets/3.7.0">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<IncludeBuildOutput>false</IncludeBuildOutput>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<None Include="buildTransitive\**\*" Pack="true" PackagePath="buildTransitive\%(RecursiveDir)%(Filename)%(Extension)" />
<None Include="..\LuckPerms.Loader\LuckPerms.Loader.csproj;..\LuckPerms.Loader\Plugin.cs" Pack="true" PackagePath="LuckPerms.Loader\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
<Project>
<Target Name="BuildArchive" AfterTargets="AfterBuild"
Condition="$(DesignTimeBuild) != true Or $(BuildingForLiveUnitTesting) == true">
<PropertyGroup>
<PluginBasePath>$(MSBuildThisFileDirectory)..\LuckPerms.Loader\$(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\</PluginBasePath>
<PluginZipPath>$(PluginBasePath)plugin.zip</PluginZipPath>
<PluginZipHashPath>$(PluginBasePath)plugin.zip.sha256</PluginZipHashPath>
<PluginNamePath>$(PluginBasePath)name.txt</PluginNamePath>
<PluginManifestPath>$(PluginBasePath)manifest.xml</PluginManifestPath>
<PluginOutputPath>$(ProjectDir)$(BaseOutputPath)$(Configuration)\plugin\</PluginOutputPath>
</PropertyGroup>
<MakeDir Directories="$(PluginBasePath)" ContinueOnError="true" />
<ZipDirectory DestinationFile="$(PluginZipPath)" SourceDirectory="$(OutputPath)" Overwrite="true" />
<GetFileHash Files="$(PluginZipPath)">
<Output TaskParameter="Hash"
ItemName="PluginZipHash" />
</GetFileHash>
<WriteLinesToFile File="$(PluginZipHashPath)" Lines="@(PluginZipHash)" Overwrite="true" />
<WriteLinesToFile File="$(PluginNamePath)" Lines="$(AssemblyName)" Overwrite="true" />
<Copy SourceFiles="$(ProjectDir)manifest.xml" DestinationFiles="$(PluginManifestPath)" SkipUnchangedFiles="true" />
<MSBuild Projects="$(MSBuildThisFileDirectory)..\LuckPerms.Loader\LuckPerms.Loader.csproj"
Properties="AssemblyName=$(AssemblyName).Loader;OutputPath=$(PluginOutputPath);Configuration=$(Configuration);TargetFrameworkRootPath=$(TargetFrameworkRootPath)"
Targets="Restore" />
<MSBuild Projects="$(MSBuildThisFileDirectory)..\LuckPerms.Loader\LuckPerms.Loader.csproj"
Properties="AssemblyName=$(AssemblyName).Loader;OutputPath=$(PluginOutputPath);Configuration=$(Configuration);TargetFrameworkRootPath=$(TargetFrameworkRootPath)" />
<RemoveDir Directories="$(PluginOutputPath)obj" />
</Target>
</Project>

View File

@@ -10,6 +10,23 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuckPerms.Torch", "LuckPerm
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuckPerms.Loader", "LuckPerms.Loader\LuckPerms.Loader.csproj", "{D1D4E971-39CE-482C-A56D-9448A77883BB}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuckPerms.Loader", "LuckPerms.Loader\LuckPerms.Loader.csproj", "{D1D4E971-39CE-482C-A56D-9448A77883BB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Loader", "Torch.Loader\Torch.Loader.csproj", "{AD9B7D1E-386A-4EF2-B475-BCB770537035}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kits", "Kits\Kits.csproj", "{DFEA7F04-6086-4D4B-A95E-100EEF793751}"
ProjectSection(ProjectDependencies) = postProject
{2C069BB5-B110-4024-93B7-28C6965AD21F} = {2C069BB5-B110-4024-93B7-28C6965AD21F}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{2C911BD8-8B11-460E-AB7E-16552949A6FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "loader", "loader", "{06CD2354-307D-4A1C-B46B-1D9EB3AAE742}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", "{557A4A51-B8ED-4CA0-866D-D18D219129F3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{862C7244-258E-4BFD-B271-9AA2D3FBE916}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "heh", "heh\heh.csproj", "{927CB303-E699-4716-A62E-232AE1125159}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -36,5 +53,27 @@ Global
{D1D4E971-39CE-482C-A56D-9448A77883BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D4E971-39CE-482C-A56D-9448A77883BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1D4E971-39CE-482C-A56D-9448A77883BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1D4E971-39CE-482C-A56D-9448A77883BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1D4E971-39CE-482C-A56D-9448A77883BB}.Release|Any CPU.Build.0 = Release|Any CPU {D1D4E971-39CE-482C-A56D-9448A77883BB}.Release|Any CPU.Build.0 = Release|Any CPU
{AD9B7D1E-386A-4EF2-B475-BCB770537035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD9B7D1E-386A-4EF2-B475-BCB770537035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD9B7D1E-386A-4EF2-B475-BCB770537035}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD9B7D1E-386A-4EF2-B475-BCB770537035}.Release|Any CPU.Build.0 = Release|Any CPU
{DFEA7F04-6086-4D4B-A95E-100EEF793751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFEA7F04-6086-4D4B-A95E-100EEF793751}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFEA7F04-6086-4D4B-A95E-100EEF793751}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFEA7F04-6086-4D4B-A95E-100EEF793751}.Release|Any CPU.Build.0 = Release|Any CPU
{927CB303-E699-4716-A62E-232AE1125159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{927CB303-E699-4716-A62E-232AE1125159}.Debug|Any CPU.Build.0 = Debug|Any CPU
{927CB303-E699-4716-A62E-232AE1125159}.Release|Any CPU.ActiveCfg = Release|Any CPU
{927CB303-E699-4716-A62E-232AE1125159}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AD9B7D1E-386A-4EF2-B475-BCB770537035} = {06CD2354-307D-4A1C-B46B-1D9EB3AAE742}
{D1D4E971-39CE-482C-A56D-9448A77883BB} = {06CD2354-307D-4A1C-B46B-1D9EB3AAE742}
{DFEA7F04-6086-4D4B-A95E-100EEF793751} = {2C911BD8-8B11-460E-AB7E-16552949A6FC}
{2C069BB5-B110-4024-93B7-28C6965AD21F} = {2C911BD8-8B11-460E-AB7E-16552949A6FC}
{3963D8F4-CCB6-4305-8FEC-A19597404A19} = {557A4A51-B8ED-4CA0-866D-D18D219129F3}
{B1A35416-6CFB-4AE7-A2F2-818E8F7A8C13} = {557A4A51-B8ED-4CA0-866D-D18D219129F3}
{8F9D910F-FFE6-4010-921F-5872ACF638BB} = {557A4A51-B8ED-4CA0-866D-D18D219129F3}
{927CB303-E699-4716-A62E-232AE1125159} = {862C7244-258E-4BFD-B271-9AA2D3FBE916}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

43
heh/DbManager.cs Normal file
View File

@@ -0,0 +1,43 @@
using System.IO;
using NLog;
using PetaPoco;
using PetaPoco.Core.Inflection;
using PetaPoco.Providers;
using Torch;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
namespace heh;
public interface IDbManager : IManager
{
IDatabase Create(string name);
}
public class DbManager : Manager, IDbManager
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
#pragma warning disable CS0618
public static readonly IDbManager Static = new DbManager(TorchBase.Instance);
#pragma warning restore CS0618
public DbManager(ITorchBase torchInstance) : base(torchInstance)
{
}
public IDatabase Create(string name)
{
return DatabaseConfiguration.Build()
.UsingProvider<SQLiteDatabaseProvider>()
.UsingExceptionThrown((_, args) => Log.Error(args.Exception))
.WithAutoSelect()
.UsingConnectionString($"Data Source={Path.Combine(Torch.Config.InstancePath, $"{name}.db")};Version=3;")
.UsingDefaultMapper<ConventionMapper>(mapper =>
{
string UnFuckIt(IInflector inflector, string s) => inflector.Underscore(s).ToLower();
mapper.InflectColumnName = UnFuckIt;
mapper.InflectTableName = UnFuckIt;
})
.Create();
}
}

3
heh/FodyWeavers.xml Normal file
View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

35
heh/StringExtensions.cs Normal file
View File

@@ -0,0 +1,35 @@
namespace heh;
// https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/StringExtensions.cs
public static class StringExtensions
{
public static bool Glob(this string value, string pattern)
{
int pos;
for (pos = 0; pattern.Length != pos; pos++)
{
switch (pattern[pos])
{
case '?':
break;
case '*':
for (var i = value.Length; i >= pos; i--)
{
if (Glob(value.Substring(i), pattern.Substring(pos + 1)))
return true;
}
return false;
default:
if (value.Length == pos || char.ToUpper(pattern[pos]) != char.ToUpper(value[pos]))
{
return false;
}
break;
}
}
return value.Length == pos;
}
}

View File

@@ -0,0 +1,71 @@
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using NLog;
namespace heh.Utils;
public abstract class ChangeListener : INotifyPropertyChanged, IDisposable
{
#region *** Members ***
protected static readonly ILogger Log = LogManager.GetCurrentClassLogger();
protected string? PropertyName;
#endregion
#region *** Abstract Members ***
protected abstract void Unsubscribe();
#endregion
#region *** INotifyPropertyChanged Members and Invoker ***
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var temp = PropertyChanged;
temp?.Invoke(this, new(propertyName));
}
#endregion
#region *** Disposable Pattern ***
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Unsubscribe();
}
}
~ChangeListener()
{
Dispose(false);
}
#endregion
#region *** Factory ***
public static ChangeListener? Create(object value, string? propertyName = null)
{
switch (value)
{
case INotifyCollectionChanged collectionChanged and IEnumerable:
return new CollectionChangeListener(collectionChanged, propertyName);
case INotifyPropertyChanged propertyChanged:
return new ChildChangeListener(propertyChanged, propertyName);
default:
Log.Warn("changes in {0} type cannot be watched", value.GetType().FullName);
return null;
}
}
#endregion
}

View File

@@ -0,0 +1,131 @@
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
namespace heh.Utils;
public class ChildChangeListener : ChangeListener
{
#region *** Members ***
protected static readonly Type InotifyType = typeof(INotifyPropertyChanged);
private readonly INotifyPropertyChanged _value;
private readonly Type _type;
private readonly Dictionary<string?, ChangeListener?> _childListeners = new();
#endregion
#region *** Constructors ***
public ChildChangeListener(INotifyPropertyChanged instance)
{
_value = instance ?? throw new ArgumentNullException(nameof(instance));
_type = _value.GetType();
Subscribe();
}
public ChildChangeListener(INotifyPropertyChanged instance, string? propertyName)
: this(instance)
{
PropertyName = propertyName;
}
#endregion
#region *** Private Methods ***
private void Subscribe()
{
_value.PropertyChanged += value_PropertyChanged;
var query =
from property
in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
where InotifyType.IsAssignableFrom(property.PropertyType)
select property;
foreach (var property in query)
{
// Declare property as known "Child", then register it
_childListeners.Add(property.Name, null);
ResetChildListener(property.Name);
}
}
/// <summary>
/// Resets known (must exist in children collection) child event handlers
/// </summary>
/// <param name="propertyName">Name of known child property</param>
private void ResetChildListener(string? propertyName)
{
if (propertyName is null || !_childListeners.TryGetValue(propertyName, out var childListener))
return;
// Unsubscribe if existing
if (childListener != null)
{
childListener.PropertyChanged -= child_PropertyChanged;
// Should unsubscribe all events
childListener.Dispose();
_childListeners.Remove(propertyName);
}
var property = _type.GetProperty(propertyName);
if (property == null)
throw new InvalidOperationException($"Was unable to get '{propertyName}' property information from Type '{_type.Name}'");
var newValue = property.GetValue(_value, null);
if (newValue is not null)
_childListeners[propertyName] = Create(newValue, propertyName);
if (_childListeners[propertyName] != null)
_childListeners[propertyName]!.PropertyChanged += child_PropertyChanged;
}
#endregion
#region *** Event Handler ***
private void child_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(e.PropertyName);
}
private void value_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// First, reset child on change, if required...
ResetChildListener(e.PropertyName);
// ...then, notify about it
RaisePropertyChanged(e.PropertyName);
}
protected override void RaisePropertyChanged(string propertyName)
{
// Special Formatting
base.RaisePropertyChanged($"{PropertyName}{(PropertyName != null ? "." : null)}{propertyName}");
}
#endregion
#region *** Overrides ***
/// <summary>
/// Release all child handlers and self handler
/// </summary>
protected override void Unsubscribe()
{
_value.PropertyChanged -= value_PropertyChanged;
foreach (var kv in _childListeners)
{
kv.Value?.Dispose();
}
_childListeners.Clear();
Debug.WriteLine("ChildChangeListener '{0}' unsubscribed", PropertyName);
}
#endregion
}

View File

@@ -0,0 +1,123 @@
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
namespace heh.Utils;
public class CollectionChangeListener : ChangeListener
{
#region *** Members ***
private readonly INotifyCollectionChanged _value;
private readonly Dictionary<INotifyPropertyChanged, ChangeListener> _collectionListeners = new();
#endregion
#region *** Constructors ***
public CollectionChangeListener(INotifyCollectionChanged collection, string? propertyName)
{
_value = collection;
PropertyName = propertyName;
if (_value.GetType().IsGenericType && !typeof(INotifyPropertyChanged).IsAssignableFrom(_value.GetType().GetGenericArguments()[0]))
return;
Subscribe();
}
#endregion
#region *** Private Methods ***
private void Subscribe()
{
_value.CollectionChanged += value_CollectionChanged;
foreach (INotifyPropertyChanged item in (IEnumerable)_value)
{
ResetChildListener(item);
}
}
private void ResetChildListener(INotifyPropertyChanged item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
RemoveItem(item);
var listener = Create(item)!;
listener.PropertyChanged += listener_PropertyChanged;
_collectionListeners.Add(item, listener);
}
private void RemoveItem(INotifyPropertyChanged item)
{
// Remove old
if (!_collectionListeners.ContainsKey(item))
return;
_collectionListeners[item].PropertyChanged -= listener_PropertyChanged;
_collectionListeners[item].Dispose();
_collectionListeners.Remove(item);
}
private void ClearCollection()
{
foreach (var key in _collectionListeners.Keys)
{
_collectionListeners[key].Dispose();
}
_collectionListeners.Clear();
}
#endregion
#region *** Event handlers ***
private void value_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
ClearCollection();
return;
}
// Don't care about e.Action, if there are old items, Remove them...
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged item in e.OldItems)
RemoveItem(item);
}
// ...add new items as well
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged item in e.NewItems)
ResetChildListener(item);
}
}
private void listener_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// ...then, notify about it
RaisePropertyChanged($"{PropertyName}{(PropertyName != null ? "[]." : null)}{e.PropertyName}");
}
#endregion
#region *** Overrides ***
/// <summary>
/// Releases all collection item handlers and self handler
/// </summary>
protected override void Unsubscribe()
{
ClearCollection();
_value.CollectionChanged -= value_CollectionChanged;
Debug.WriteLine("CollectionChangeListener unsubscribed");
}
#endregion
}

View File

@@ -0,0 +1,84 @@
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using NLog;
using Torch;
namespace heh.Utils;
/// <summary>
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using XML serialization.
/// Will automatically save on changes in the data class.
/// </summary>
/// <typeparam name="TViewModel">Data class type</typeparam>
public sealed class ProperPersistent<TViewModel> : IDisposable where TViewModel : class, INotifyPropertyChanged, new()
{
private static readonly XmlSerializer Serializer = new(typeof(TViewModel));
private static readonly ILogger Log = LogManager.GetLogger($"ProperPersistent_{typeof(TViewModel)}");
private readonly ChangeListener _listener;
private Timer? _saveConfigTimer;
public TViewModel Data { get; }
public string Path { get; set; }
public ProperPersistent(string path, TViewModel? defaultViewModel = default)
{
Path = path;
if (File.Exists(path))
{
try
{
using var stream = File.OpenRead(path);
Data = (TViewModel) Serializer.Deserialize(stream);
}
catch (Exception e)
{
Log.Error(e);
Data = defaultViewModel ?? new TViewModel();
}
}
else
{
Data = defaultViewModel ?? new TViewModel();
Save();
}
_listener = ChangeListener.Create(Data)!;
_listener.PropertyChanged += ListenerOnPropertyChanged;
}
private void ListenerOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
SaveAsync();
}
private void SaveAsync()
{
_saveConfigTimer ??= new(_ => Save());
_saveConfigTimer.Change(1000, -1);
}
public void Dispose()
{
_listener.Dispose();
_saveConfigTimer?.Dispose();
_saveConfigTimer = null;
}
#region Backwards compatibility
public void Save(string? newPath = null)
{
if (newPath is not null)
Path = newPath;
using var stream = File.Create(Path);
using var writer = new XmlTextWriter(stream, Encoding.UTF8)
{
Formatting = Formatting.Indented
};
Serializer.Serialize(writer, Data);
}
public static ProperPersistent<TViewModel> Load(string path, bool saveIfNew = true) => new(path);
#endregion
}

21
heh/heh.csproj Normal file
View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>10</LangVersion>
<UseWpf>true</UseWpf>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PetaPoco.Compiled" Version="6.0.480" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="Torch.Server.ReferenceAssemblies" Version="1.3.1.207-master" PrivateAssets="all" IncludeAssets="compile" />
</ItemGroup>
</Project>