mirror of
https://github.com/oliverbooth/VpSharp
synced 2024-11-22 17:58:48 +00:00
VpSharp.Says("Hello World");
This commit is contained in:
parent
eda59e9d7e
commit
56aae0116e
22
VpSharp.Tests/VpSharp.Tests.csproj
Normal file
22
VpSharp.Tests/VpSharp.Tests.csproj
Normal file
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VpSharp\VpSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
34
VpSharp.Tests/src/ColorFTests.cs
Normal file
34
VpSharp.Tests/src/ColorFTests.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace VpSharp.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class ColorFTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Black_ToVpColorF_ShouldGive0ForProperties()
|
||||
{
|
||||
ColorF color = Color.Black;
|
||||
Assert.AreEqual(color.A, 1.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.R, 0.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.G, 0.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.B, 0.0f, float.Epsilon);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Transparent_ToVpColorF_ShouldGive0ForAlpha()
|
||||
{
|
||||
ColorF color = Color.Transparent;
|
||||
Assert.AreEqual(color.A, 0.0f, float.Epsilon);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void White_ToVpColorF_ShouldGive1ForProperties()
|
||||
{
|
||||
ColorF color = Color.White;
|
||||
Assert.AreEqual(color.A, 1.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.R, 1.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.G, 1.0f, float.Epsilon);
|
||||
Assert.AreEqual(color.B, 1.0f, float.Epsilon);
|
||||
}
|
||||
}
|
1
VpSharp.Tests/src/Usings.cs
Normal file
1
VpSharp.Tests/src/Usings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
52
VpSharp/VpSharp.csproj
Normal file
52
VpSharp/VpSharp.csproj
Normal file
@ -0,0 +1,52 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<VersionPrefix>0.1.0</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' == ''">
|
||||
<Version>$(VersionPrefix)-$(VersionSuffix)</Version>
|
||||
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||
<PackageVersion>$(VersionPrefix)-$(VersionSuffix)</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">
|
||||
<Version>$(VersionPrefix)-$(VersionSuffix).$(BuildNumber)</Version>
|
||||
<AssemblyVersion>$(VersionPrefix).$(BuildNumber)</AssemblyVersion>
|
||||
<FileVersion>$(VersionPrefix).$(BuildNumber)</FileVersion>
|
||||
<PackageVersion>$(VersionPrefix)-$(VersionSuffix).$(BuildNumber)</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VersionSuffix)' == ''">
|
||||
<Version>$(VersionPrefix)</Version>
|
||||
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||
<PackageVersion>$(VersionPrefix)</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="ZString" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="res\ExceptionMessages.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>ExceptionMessages.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="res\ExceptionMessages.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ExceptionMessages.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
2
VpSharp/VpSharp.csproj.DotSettings
Normal file
2
VpSharp/VpSharp.csproj.DotSettings
Normal file
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=res/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
125
VpSharp/res/ExceptionMessages.Designer.cs
generated
Normal file
125
VpSharp/res/ExceptionMessages.Designer.cs
generated
Normal file
@ -0,0 +1,125 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace VpSharp {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class ExceptionMessages {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal ExceptionMessages() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VpSharp.ExceptionMessages", typeof(ExceptionMessages).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The operation cannot be performed on the client's current avatar..
|
||||
/// </summary>
|
||||
internal static string CannotUseSelf {
|
||||
get {
|
||||
return ResourceManager.GetString("CannotUseSelf", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An attempt was made to perform an operation that requires a connection to a world server..
|
||||
/// </summary>
|
||||
internal static string NotInWorld {
|
||||
get {
|
||||
return ResourceManager.GetString("NotInWorld", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An attempt was made to perform an operation on a nonexistent object..
|
||||
/// </summary>
|
||||
internal static string ObjectNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("ObjectNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A specified value is too long..
|
||||
/// </summary>
|
||||
internal static string StringTooLong {
|
||||
get {
|
||||
return ResourceManager.GetString("StringTooLong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The specified remote endpoint is not supported..
|
||||
/// </summary>
|
||||
internal static string UnsupportedEndpoint {
|
||||
get {
|
||||
return ResourceManager.GetString("UnsupportedEndpoint", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to World name cannot be null or empty..
|
||||
/// </summary>
|
||||
internal static string WorldNameCannotBeEmpty {
|
||||
get {
|
||||
return ResourceManager.GetString("WorldNameCannotBeEmpty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Value must be 0.0 through 1.0..
|
||||
/// </summary>
|
||||
internal static string ZeroThroughOne {
|
||||
get {
|
||||
return ResourceManager.GetString("ZeroThroughOne", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
VpSharp/res/ExceptionMessages.resx
Normal file
43
VpSharp/res/ExceptionMessages.resx
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<data name="CannotUseSelf" xml:space="preserve">
|
||||
<value>The operation cannot be performed on the client's current avatar.</value>
|
||||
</data>
|
||||
<data name="NotInWorld" xml:space="preserve">
|
||||
<value>An attempt was made to perform an operation that requires a connection to a world server.</value>
|
||||
</data>
|
||||
<data name="ObjectNotFound" xml:space="preserve">
|
||||
<value>An attempt was made to perform an operation on a nonexistent object.</value>
|
||||
</data>
|
||||
<data name="StringTooLong" xml:space="preserve">
|
||||
<value>A specified value is too long.</value>
|
||||
</data>
|
||||
<data name="UnsupportedEndpoint" xml:space="preserve">
|
||||
<value>The specified remote endpoint is not supported.</value>
|
||||
</data>
|
||||
<data name="WorldNameCannotBeEmpty" xml:space="preserve">
|
||||
<value>World name cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="ZeroThroughOne" xml:space="preserve">
|
||||
<value>Value must be 0.0 through 1.0.</value>
|
||||
</data>
|
||||
</root>
|
43
VpSharp/src/Application.cs
Normal file
43
VpSharp/src/Application.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an application.
|
||||
/// </summary>
|
||||
public sealed class Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Application" /> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this application.</param>
|
||||
/// <param name="version">The version of this application.</param>
|
||||
public Application(string name, string version)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(version)) Version = null;
|
||||
else Version = version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this application.
|
||||
/// </summary>
|
||||
/// <value>The name of this application.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of this application.
|
||||
/// </summary>
|
||||
/// <value>The version of this application.</value>
|
||||
public string? Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructs this instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The application name.</param>
|
||||
/// <param name="version">The application version./</param>
|
||||
public void Deconstruct(out string name, out string? version)
|
||||
{
|
||||
name = Name;
|
||||
version = Version;
|
||||
}
|
||||
}
|
1
VpSharp/src/Assembly.cs
Normal file
1
VpSharp/src/Assembly.cs
Normal file
@ -0,0 +1 @@
|
||||
[assembly: CLSCompliant(true)]
|
17
VpSharp/src/BumpPhase.cs
Normal file
17
VpSharp/src/BumpPhase.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of object collision phases.
|
||||
/// </summary>
|
||||
public enum BumpPhase
|
||||
{
|
||||
/// <summary>
|
||||
/// The collision has started.
|
||||
/// </summary>
|
||||
Begin,
|
||||
|
||||
/// <summary>
|
||||
/// The collision has ended.
|
||||
/// </summary>
|
||||
End
|
||||
}
|
153
VpSharp/src/Cell.cs
Normal file
153
VpSharp/src/Cell.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a cell.
|
||||
/// </summary>
|
||||
public readonly struct Cell : IEquatable<Cell>, IFormattable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Cell" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the cell.</param>
|
||||
/// <param name="z">The Z coordinate of the cell.</param>
|
||||
public Cell(int x, int z)
|
||||
{
|
||||
X = x;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X coordinate of this cell.
|
||||
/// </summary>
|
||||
/// <value>The X coordinate.</value>
|
||||
public int X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Z coordinate of this cell.
|
||||
/// </summary>
|
||||
/// <value>The Z coordinate.</value>
|
||||
public int Z { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given cells are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first cell to compare.</param>
|
||||
/// <param name="right">The second cell to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two cells are equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator ==(Cell left, Cell right) => left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given cells are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first cell to compare.</param>
|
||||
/// <param name="right">The second cell to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two cells are equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator !=(Cell left, Cell right) => !left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly converts an instance of <see cref="Vector2" /> to an instance of <see cref="Cell" />.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to convert.</param>
|
||||
/// <returns>
|
||||
/// A cell whose <see cref="X" /> component is equal to <see cref="Vector2.X" />, and whose <see cref="Z" /> component
|
||||
/// is equal to <see cref="Vector2.Y" />.
|
||||
/// </returns>
|
||||
public static explicit operator Cell(Vector2 vector) => new((int)vector.X, (int)vector.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an instance of <see cref="Cell" /> to an instance of <see cref="Vector2" />.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell to convert.</param>
|
||||
/// <returns>
|
||||
/// A vector whose <see cref="X" /> component is equal to <see cref="Cell.X" />, and whose <see cref="Vector2.Y" />
|
||||
/// component is equal to <see cref="Z" />.
|
||||
/// </returns>
|
||||
public static implicit operator Vector2(Cell cell) => new(cell.X, cell.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly converts an instance of <see cref="Vector3" /> to an instance of <see cref="Cell" />.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to convert.</param>
|
||||
/// <returns>
|
||||
/// A cell whose <see cref="X" /> component is equal to <see cref="Vector3.X" />, and whose <see cref="Z" /> component
|
||||
/// is equal to <see cref="Vector3.Z" />.
|
||||
/// </returns>
|
||||
public static explicit operator Cell(Vector3 vector) => new((int)vector.X, (int)vector.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an instance of <see cref="Cell" /> to an instance of <see cref="Vector3" />.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell to convert.</param>
|
||||
/// <returns>
|
||||
/// A vector whose <see cref="X" /> component is equal to <see cref="Cell.X" />, and whose <see cref="Vector3.Z" />
|
||||
/// component is equal to <see cref="Z" />, and whose <see cref="Vector3.Y" /> component is 0.
|
||||
/// </returns>
|
||||
public static implicit operator Vector3(Cell cell) => new(cell.X, 0, cell.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly converts an instance of <see cref="Vector3d" /> to an instance of <see cref="Cell" />.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to convert.</param>
|
||||
/// <returns>
|
||||
/// A cell whose <see cref="X" /> component is equal to <see cref="Vector3d.X" />, and whose <see cref="Z" />
|
||||
/// component is equal to <see cref="Vector3d.Z" />.
|
||||
/// </returns>
|
||||
public static explicit operator Cell(Vector3d vector) => new((int)vector.X, (int)vector.Z);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this cell and another cell are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The cell to compare with this instance.</param>
|
||||
/// <returns><see langword="true" /> if the two cells are equal; otherwise, <see langword="false" />.</returns>
|
||||
public bool Equals(Cell other) => X == other.X && Z == other.Z;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj) => obj is Cell other && Equals(other);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => HashCode.Combine(X, Z);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> representing this <see cref="Vector3d" /> instance.
|
||||
/// </summary>
|
||||
/// <returns>The string representation.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString("G", CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> representing this <see cref="Vector3d" /> instance, using the specified format to
|
||||
/// format individual elements.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of individual elements.</param>
|
||||
/// <returns>The string representation.</returns>
|
||||
public readonly string ToString(string? format)
|
||||
{
|
||||
return ToString(format, CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> representing this <see cref="Vector3d" /> instance, using the specified format to
|
||||
/// format individual elements and the given <see cref="IFormatProvider" />.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of individual elements.</param>
|
||||
/// <param name="formatProvider">The format provider to use when formatting elements.</param>
|
||||
/// <returns>The string representation.</returns>
|
||||
public readonly string ToString(string? format, IFormatProvider? formatProvider)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;
|
||||
builder.Append('<');
|
||||
builder.Append(X.ToString(format, formatProvider));
|
||||
builder.Append(separator);
|
||||
builder.Append(' ');
|
||||
builder.Append(Z.ToString(format, formatProvider));
|
||||
builder.Append('>');
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
148
VpSharp/src/ColorF.cs
Normal file
148
VpSharp/src/ColorF.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System.Drawing;
|
||||
using VpSharp.Internal;
|
||||
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color composed of single-precision floating point values. This structure is intended to behave similarly
|
||||
/// to <see cref="Color" />, but uses <see cref="float" /> components rather than <see cref="byte" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="Color" />
|
||||
public readonly struct ColorF : IEquatable<ColorF>
|
||||
{
|
||||
private ColorF(float a, float r, float g, float b)
|
||||
{
|
||||
A = a;
|
||||
R = r;
|
||||
G = g;
|
||||
B = b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the alpha component value of this <see cref="ColorF" /> structure.
|
||||
/// </summary>
|
||||
/// <value>The alpha component value of this <see cref="ColorF" />.</value>
|
||||
public float A { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the red component value of this <see cref="ColorF" /> structure.
|
||||
/// </summary>
|
||||
/// <value>The red component value of this <see cref="ColorF" />.</value>
|
||||
public float R { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the green component value of this <see cref="ColorF" /> structure.
|
||||
/// </summary>
|
||||
/// <value>The green component value of this <see cref="ColorF" />.</value>
|
||||
public float G { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the blue component value of this <see cref="ColorF" /> structure.
|
||||
/// </summary>
|
||||
/// <value>The blue component value of this <see cref="ColorF" />.</value>
|
||||
public float B { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given colors are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first color to compare.</param>
|
||||
/// <param name="right">The second color to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two colors are equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator ==(ColorF left, ColorF right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given colors are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first color to compare.</param>
|
||||
/// <param name="right">The second color to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two colors are equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator !=(ColorF left, ColorF right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an instance of <see cref="Color" /> to an instance of <see cref="ColorF" />.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to convert.</param>
|
||||
/// <returns>The converted color.</returns>
|
||||
public static implicit operator ColorF(Color color)
|
||||
{
|
||||
return FromArgb(color.A / 255.0f, color.R / 255.0f, color.G / 255.0f, color.B / 255.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an instance of <see cref="ColorF" /> to an instance of <see cref="Color" />.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to convert.</param>
|
||||
/// <returns>The converted color.</returns>
|
||||
public static explicit operator Color(ColorF color)
|
||||
{
|
||||
return Color.FromArgb((int) (color.A * 255.0f), (int) (color.R * 255.0f), (int) (color.G * 255.0f),
|
||||
(int) (color.B * 255.0f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ColorF" /> structure from the specified single-precision floating-point color values (red,
|
||||
/// green, and blue). The alpha value is implicitly 1.0 (fully opaque).
|
||||
/// </summary>
|
||||
/// <param name="r">The red component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <param name="g">The green component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <param name="b">The blue component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <returns>The <see cref="ColorF" /> that this method creates.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="r" />, <paramref name="g" />, or <paramref name="b" /> is less than 0 or greater than 1.
|
||||
/// </exception>
|
||||
public static ColorF FromArgb(float r, float g, float b)
|
||||
{
|
||||
return FromArgb(1.0f, r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ColorF" /> structure from the specified single-precision floating-point color values (alpha,
|
||||
/// red, green, and blue).
|
||||
/// </summary>
|
||||
/// <param name="a">The alpha component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <param name="r">The red component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <param name="g">The green component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <param name="b">The blue component value for the new <see cref="ColorF" />. Valid values are 0.0 through 1.0.</param>
|
||||
/// <returns>The <see cref="ColorF" /> that this method creates.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="a" />, <paramref name="r" />, <paramref name="g" />, or <paramref name="b" /> is less than 0 or
|
||||
/// greater than 1.
|
||||
/// </exception>
|
||||
public static ColorF FromArgb(float a, float r, float g, float b)
|
||||
{
|
||||
if (a is < 0 or > 1) throw ThrowHelper.ZeroThroughOneException(nameof(a));
|
||||
if (r is < 0 or > 1) throw ThrowHelper.ZeroThroughOneException(nameof(r));
|
||||
if (g is < 0 or > 1) throw ThrowHelper.ZeroThroughOneException(nameof(g));
|
||||
if (b is < 0 or > 1) throw ThrowHelper.ZeroThroughOneException(nameof(b));
|
||||
|
||||
return new ColorF(a, r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this color and another color are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The color to compare with this instance.</param>
|
||||
/// <returns><see langword="true" /> if the two colors are equal; otherwise, <see langword="false" />.</returns>
|
||||
public bool Equals(ColorF other)
|
||||
{
|
||||
return A.Equals(other.A) && R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ColorF other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(A, R, G, B);
|
||||
}
|
||||
}
|
11
VpSharp/src/ColorSpace.cs
Normal file
11
VpSharp/src/ColorSpace.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of color spaces.
|
||||
/// </summary>
|
||||
public enum ColorSpace
|
||||
{
|
||||
Linear,
|
||||
// ReSharper disable once InconsistentNaming
|
||||
sRGB
|
||||
}
|
17
VpSharp/src/DisconnectReason.cs
Normal file
17
VpSharp/src/DisconnectReason.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of reasons for a disconnect.
|
||||
/// </summary>
|
||||
public enum DisconnectReason
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that connection to the server was lost unexpectedly.
|
||||
/// </summary>
|
||||
ConnectionLost,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that disconnection was requested and graceful.
|
||||
/// </summary>
|
||||
Disconnected
|
||||
}
|
306
VpSharp/src/Entities/VirtualParadiseAvatar.cs
Normal file
306
VpSharp/src/Entities/VirtualParadiseAvatar.cs
Normal file
@ -0,0 +1,306 @@
|
||||
using System.Numerics;
|
||||
using VpSharp.Extensions;
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
using static VpSharp.Internal.Native;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an avatar within a world.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseAvatar : IEquatable<VirtualParadiseAvatar>
|
||||
{
|
||||
private readonly VirtualParadiseClient _client;
|
||||
|
||||
internal VirtualParadiseAvatar(VirtualParadiseClient client, int session)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
Session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of the application this avatar is using.
|
||||
/// </summary>
|
||||
/// <value>The avatar's application details.</value>
|
||||
public Application Application { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this avatar is a bot.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if this avatar is a bot; otherwise, <see langword="false" />.</value>
|
||||
public bool IsBot => Name is {Length: > 1} name && name[0] == '[' && name[^1] == ']';
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of this avatar.
|
||||
/// </summary>
|
||||
/// <value>The location of this avatar.</value>
|
||||
public Location Location { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this avatar.
|
||||
/// </summary>
|
||||
/// <value>The name of this avatar.</value>
|
||||
public string Name { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session ID of this avatar.
|
||||
/// </summary>
|
||||
/// <value>The session ID.</value>
|
||||
public int Session { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this avatar.
|
||||
/// </summary>
|
||||
/// <value>The type of this avatar.</value>
|
||||
public int Type { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user associated with this avatar.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public VirtualParadiseUser User { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseAvatar" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator ==(VirtualParadiseAvatar? left, VirtualParadiseAvatar? right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseAvatar" /> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator !=(VirtualParadiseAvatar? left, VirtualParadiseAvatar? right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseAvatar" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if this instance is equal to <paramref name="other" />; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public bool Equals(VirtualParadiseAvatar? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Session == other.Session && User.Equals(other.User);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is VirtualParadiseAvatar other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// ReSharper disable NonReadonlyMemberInGetHashCode
|
||||
return HashCode.Combine(Session, User);
|
||||
// ReSharper restore NonReadonlyMemberInGetHashCode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clicks this avatar.
|
||||
/// </summary>
|
||||
/// <param name="clickPoint">The position at which the avatar should be clicked.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <para>The action cannot be performed on the client's current avatar.</para>
|
||||
/// -or-
|
||||
/// <para>An attempt was made to click an avatar outside of a world.</para>
|
||||
/// </exception>
|
||||
public Task ClickAsync(Vector3d? clickPoint = null)
|
||||
{
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
if (this == _client.CurrentAvatar)
|
||||
return Task.FromException(ThrowHelper.CannotUseSelfException());
|
||||
|
||||
clickPoint ??= Location.Position;
|
||||
(double x, double y, double z) = clickPoint.Value;
|
||||
|
||||
lock (_client.Lock)
|
||||
{
|
||||
IntPtr handle = _client.NativeInstanceHandle;
|
||||
|
||||
vp_double_set(handle, FloatAttribute.ClickHitX, x);
|
||||
vp_double_set(handle, FloatAttribute.ClickHitY, y);
|
||||
vp_double_set(handle, FloatAttribute.ClickHitZ, z);
|
||||
|
||||
var reason = (ReasonCode) vp_avatar_click(handle, Session);
|
||||
if (reason == ReasonCode.NotInWorld)
|
||||
return Task.FromException(ThrowHelper.NotInWorldException());
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a URI to this avatar.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI to send.</param>
|
||||
/// <param name="target">The URL target. See <see cref="UriTarget" /> for more information.</param>
|
||||
/// <exception cref="InvalidOperationException">The action cannot be performed on the client's current avatar.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="uri" /> is <see langword="null" />.</exception>
|
||||
public Task SendUriAsync(Uri uri, UriTarget target = UriTarget.Browser)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(uri);
|
||||
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
if (this == _client.CurrentAvatar)
|
||||
return Task.FromException(ThrowHelper.CannotUseSelfException());
|
||||
|
||||
lock (_client.Lock)
|
||||
{
|
||||
vp_url_send(_client.NativeInstanceHandle, Session, uri.ToString(), target);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to another world.
|
||||
/// </summary>
|
||||
/// <param name="world">The name of the world to which this avatar should be teleported.</param>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
public Task TeleportAsync(VirtualParadiseWorld world, Vector3d position)
|
||||
{
|
||||
return TeleportAsync(world.Name, position, Quaternion.Identity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to another world.
|
||||
/// </summary>
|
||||
/// <param name="world">The name of the world to which this avatar should be teleported.</param>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
/// <param name="rotation">The rotation to which this avatar should be teleported.</param>
|
||||
public Task TeleportAsync(VirtualParadiseWorld world, Vector3d position, Quaternion rotation)
|
||||
{
|
||||
return TeleportAsync(world.Name, position, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to another world.
|
||||
/// </summary>
|
||||
/// <param name="world">The name of the world to which this avatar should be teleported.</param>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
public Task TeleportAsync(string world, Vector3d position)
|
||||
{
|
||||
return TeleportAsync(world, position, Quaternion.Identity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to another world.
|
||||
/// </summary>
|
||||
/// <param name="world">The name of the world to which this avatar should be teleported.</param>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
/// <param name="rotation">The rotation to which this avatar should be teleported.</param>
|
||||
public async Task TeleportAsync(string world, Vector3d position, Quaternion rotation)
|
||||
{
|
||||
// ReSharper disable InconsistentlySynchronizedField
|
||||
bool isSelf = this == _client.CurrentAvatar;
|
||||
bool isNewWorld = world != Location.World.Name;
|
||||
|
||||
if (world == Location.World.Name)
|
||||
{
|
||||
world = string.Empty;
|
||||
}
|
||||
|
||||
if (isSelf && isNewWorld)
|
||||
{
|
||||
await _client.EnterAsync(world);
|
||||
}
|
||||
|
||||
IntPtr handle = _client.NativeInstanceHandle;
|
||||
|
||||
if (this == _client.CurrentAvatar)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(world))
|
||||
{
|
||||
await _client.EnterAsync(world);
|
||||
}
|
||||
|
||||
// state change self
|
||||
lock (_client.Lock)
|
||||
{
|
||||
(double x, double y, double z) = position;
|
||||
(double pitch, double yaw, double _) = rotation.ToEulerAngles(false);
|
||||
|
||||
vp_double_set(handle, FloatAttribute.MyX, x);
|
||||
vp_double_set(handle, FloatAttribute.MyY, y);
|
||||
vp_double_set(handle, FloatAttribute.MyZ, z);
|
||||
vp_double_set(handle, FloatAttribute.MyPitch, pitch);
|
||||
vp_double_set(handle, FloatAttribute.MyYaw, yaw);
|
||||
|
||||
var reason = (ReasonCode) vp_state_change(handle);
|
||||
if (reason == ReasonCode.NotInWorld)
|
||||
ThrowHelper.ThrowNotInWorldException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_client.Lock)
|
||||
{
|
||||
(float x, float y, float z) = (Vector3) position;
|
||||
(float pitch, float yaw, float _) = (Vector3) rotation.ToEulerAngles(false);
|
||||
|
||||
var reason = (ReasonCode) vp_teleport_avatar(handle, Session, world, x, y, z, yaw, pitch);
|
||||
if (reason == ReasonCode.NotInWorld)
|
||||
ThrowHelper.ThrowNotInWorldException();
|
||||
}
|
||||
}
|
||||
|
||||
Location = new Location(new VirtualParadiseWorld(_client, world), position, rotation);
|
||||
// ReSharper restore InconsistentlySynchronizedField
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to a new position within the current world.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
public Task TeleportAsync(Vector3d position)
|
||||
{
|
||||
return TeleportAsync(Location with {Position = position});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the avatar to a new position and rotation within the current world.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to which this avatar should be teleported.</param>
|
||||
/// <param name="rotation">The rotation to which this avatar should be teleported</param>
|
||||
public Task TeleportAsync(Vector3d position, Quaternion rotation)
|
||||
{
|
||||
return TeleportAsync(Location with {Position = position, Rotation = rotation});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports this avatar to a new location, which may or may not be a new world.
|
||||
/// </summary>
|
||||
/// <param name="location">The location to which this avatar should be teleported.</param>
|
||||
public Task TeleportAsync(Location location)
|
||||
{
|
||||
return TeleportAsync(location.World.Name, location.Position, location.Rotation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Avatar [Session={Session}, Name={Name}]";
|
||||
}
|
||||
}
|
61
VpSharp/src/Entities/VirtualParadiseMessage.cs
Normal file
61
VpSharp/src/Entities/VirtualParadiseMessage.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseMessage
|
||||
{
|
||||
internal VirtualParadiseMessage(
|
||||
MessageType type,
|
||||
string name,
|
||||
string content,
|
||||
VirtualParadiseAvatar author,
|
||||
FontStyle style,
|
||||
Color color)
|
||||
{
|
||||
Type = type;
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name;
|
||||
Content = content;
|
||||
Author = author;
|
||||
Style = style;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message author.
|
||||
/// </summary>
|
||||
/// <value>The message author.</value>
|
||||
public VirtualParadiseAvatar Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message content.
|
||||
/// </summary>
|
||||
/// <value>The message content.</value>
|
||||
public string Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message name.
|
||||
/// </summary>
|
||||
/// <value>The message name.This will always be equal to the name of the <see cref="Author" /> for chat messages.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message color.
|
||||
/// </summary>
|
||||
/// <value>The message color. This will always be <see cref="System.Drawing.Color.Black" /> for chat messages.</value>
|
||||
public Color Color { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message font style.
|
||||
/// </summary>
|
||||
/// <value>The message font style. This will always be <see cref="FontStyle.Regular" /> for chat messages.</value>
|
||||
public FontStyle Style { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this message.
|
||||
/// </summary>
|
||||
/// <value>The type of this message.</value>
|
||||
public MessageType Type { get; }
|
||||
}
|
82
VpSharp/src/Entities/VirtualParadiseModelObject.cs
Normal file
82
VpSharp/src/Entities/VirtualParadiseModelObject.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
using static VpSharp.Internal.Native;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object which renders as a 3D model. A "model" object will contain a <c>Model</c>, <c>Description</c>
|
||||
/// and <c>Action</c> field.
|
||||
/// </summary>
|
||||
public class VirtualParadiseModelObject : VirtualParadiseObject
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal VirtualParadiseModelObject(VirtualParadiseClient client, int id)
|
||||
: base(client, id)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of this object's <c>Description</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Description</c> field.</value>
|
||||
public string Action { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of this object's <c>Description</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Description</c> field.</value>
|
||||
public string Description { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of this object's <c>Model</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Model</c> field.</value>
|
||||
public string Model { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the object.
|
||||
/// </summary>
|
||||
/// <param name="action">The builder which defines parameters to change.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="action" /> is <see langword="null" />.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <para><see cref="VirtualParadiseModelObjectBuilder.ModificationTimestamp" /> was assigned.</para>
|
||||
/// -or-
|
||||
/// <para><see cref="VirtualParadiseModelObjectBuilder.Owner" /> was assigned.</para>
|
||||
/// </exception>
|
||||
public async ValueTask ModifyAsync(Action<VirtualParadiseModelObjectBuilder> action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
var builder = new VirtualParadiseModelObjectBuilder(Client, ObjectBuilderMode.Modify);
|
||||
await Task.Run(() => action(builder));
|
||||
|
||||
lock (Client.Lock)
|
||||
{
|
||||
IntPtr handle = Client.NativeInstanceHandle;
|
||||
vp_int_set(handle, IntegerAttribute.ObjectId, Id);
|
||||
builder.ApplyChanges();
|
||||
|
||||
vp_object_change(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void ExtractFromOther(VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
if (virtualParadiseObject is not VirtualParadiseModelObject model)
|
||||
return;
|
||||
|
||||
Action = model.Action;
|
||||
Description = model.Description;
|
||||
Model = model.Model;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void ExtractFromInstance(IntPtr handle)
|
||||
{
|
||||
Action = vp_string(handle, StringAttribute.ObjectAction);
|
||||
Description = vp_string(handle, StringAttribute.ObjectDescription);
|
||||
Model = vp_string(handle, StringAttribute.ObjectModel);
|
||||
}
|
||||
}
|
163
VpSharp/src/Entities/VirtualParadiseModelObjectBuilder.cs
Normal file
163
VpSharp/src/Entities/VirtualParadiseModelObjectBuilder.cs
Normal file
@ -0,0 +1,163 @@
|
||||
using System.Numerics;
|
||||
using VpSharp.Extensions;
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
using static VpSharp.Internal.Native;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides mutability for <see cref="VirtualParadiseObject" />.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseModelObjectBuilder : VirtualParadiseObjectBuilder
|
||||
{
|
||||
internal VirtualParadiseModelObjectBuilder(VirtualParadiseClient client, ObjectBuilderMode mode)
|
||||
: base(client, mode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this object's <c>Action</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Action</c> field, or <see langword="null" /> to leave unchanged.</value>
|
||||
public string? Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this object's <c>Description</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Description</c> field, or <see langword="null" /> to leave unchanged.</value>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this object's <c>Model</c> field.
|
||||
/// </summary>
|
||||
/// <value>The value of this object's <c>Model</c> field, or <see langword="null" /> to leave unchanged.</value>
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time at which this object was last modified.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The date and time at which this object was last modified, or <see langword="null" /> to leave unchanged.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This property may only be set during an object load, and will throw <see cref="InvalidOperationException" /> at
|
||||
/// any other point.
|
||||
/// </remarks>
|
||||
public DateTimeOffset? ModificationTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner of this object.
|
||||
/// </summary>
|
||||
/// <value>The owner of this object, or <see langword="null" /> to leave unchanged.</value>
|
||||
/// <remarks>
|
||||
/// This property may only be set during an object load, and will throw <see cref="InvalidOperationException" /> at
|
||||
/// any other point.
|
||||
/// </remarks>
|
||||
public VirtualParadiseUser? Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the object.
|
||||
/// </summary>
|
||||
/// <value>The position of the object, or <see langword="null" /> to leave unchanged.</value>
|
||||
public Vector3d? Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation of the object.
|
||||
/// </summary>
|
||||
/// <value>The rotation of the object, or <see langword="null" /> to leave unchanged.</value>
|
||||
public Quaternion? Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of this object's <c>Action</c> field.
|
||||
/// </summary>
|
||||
/// <param name="action">The new value of the <c>Action</c> field, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance of this builder.</returns>
|
||||
public VirtualParadiseModelObjectBuilder WithAction(string? action)
|
||||
{
|
||||
Action = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of this object's <c>Description</c> field.
|
||||
/// </summary>
|
||||
/// <param name="description">
|
||||
/// The new value of the <c>Description</c> field, or <see langword="null" /> to leave unchanged.
|
||||
/// </param>
|
||||
/// <returns>The current instance of this builder.</returns>
|
||||
public VirtualParadiseModelObjectBuilder WithDescription(string? description)
|
||||
{
|
||||
Description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of this object's <c>Model</c> field.
|
||||
/// </summary>
|
||||
/// <param name="model">The new value of the <c>Model</c> field, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance of this builder.</returns>
|
||||
public VirtualParadiseModelObjectBuilder WithModel(string? model)
|
||||
{
|
||||
Model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal void ApplyChanges()
|
||||
{
|
||||
IntPtr handle = Client.NativeInstanceHandle;
|
||||
|
||||
if (Action is { } action) vp_string_set(handle, StringAttribute.ObjectAction, action);
|
||||
if (Description is { } description) vp_string_set(handle, StringAttribute.ObjectDescription, description);
|
||||
if (Model is { } model) vp_string_set(handle, StringAttribute.ObjectModel, model);
|
||||
|
||||
if (Position is { } position)
|
||||
{
|
||||
(double x, double y, double z) = position;
|
||||
vp_double_set(handle, FloatAttribute.ObjectX, x);
|
||||
vp_double_set(handle, FloatAttribute.ObjectY, y);
|
||||
vp_double_set(handle, FloatAttribute.ObjectZ, z);
|
||||
}
|
||||
else if (Mode == ObjectBuilderMode.Create)
|
||||
{
|
||||
throw new ArgumentException("Position must be assigned when creating a new object.");
|
||||
}
|
||||
|
||||
if (Rotation is null && Mode == ObjectBuilderMode.Create)
|
||||
{
|
||||
Rotation = Quaternion.Identity;
|
||||
}
|
||||
|
||||
if (Rotation is { } rotation)
|
||||
{
|
||||
(double x, double y, double z) = Vector3d.Zero;
|
||||
double angle = double.PositiveInfinity;
|
||||
if (rotation != Quaternion.Identity)
|
||||
{
|
||||
rotation.ToAxisAngle(out Vector3d axis, out angle);
|
||||
(x, y, z) = axis;
|
||||
}
|
||||
|
||||
vp_double_set(handle, FloatAttribute.ObjectRotationX, x);
|
||||
vp_double_set(handle, FloatAttribute.ObjectRotationY, y);
|
||||
vp_double_set(handle, FloatAttribute.ObjectRotationZ, z);
|
||||
vp_double_set(handle, FloatAttribute.ObjectRotationAngle, angle);
|
||||
}
|
||||
|
||||
if (ModificationTimestamp is { } modificationTimestamp)
|
||||
{
|
||||
if (Mode != ObjectBuilderMode.Load)
|
||||
throw new InvalidOperationException("Modification timestamp can only be assigned during an object load.");
|
||||
|
||||
vp_int_set(handle, IntegerAttribute.ObjectTime, (int) modificationTimestamp.ToUnixTimeSeconds());
|
||||
}
|
||||
|
||||
if (Owner is { } owner)
|
||||
{
|
||||
if (Mode != ObjectBuilderMode.Load)
|
||||
throw new InvalidOperationException("Owner can only be assigned during an object load.");
|
||||
|
||||
vp_int_set(handle, IntegerAttribute.ObjectUserId, owner.Id);
|
||||
}
|
||||
}
|
||||
}
|
208
VpSharp/src/Entities/VirtualParadiseObject.cs
Normal file
208
VpSharp/src/Entities/VirtualParadiseObject.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using System.Numerics;
|
||||
using VpSharp.Exceptions;
|
||||
using VpSharp.Extensions;
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
using static VpSharp.Internal.Native;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base class for all in-world objects.
|
||||
/// </summary>
|
||||
public abstract class VirtualParadiseObject : IEquatable<VirtualParadiseObject>
|
||||
{
|
||||
protected internal VirtualParadiseObject(VirtualParadiseClient client, int id)
|
||||
{
|
||||
Client = client;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this object.
|
||||
/// </summary>
|
||||
/// <value>The unique ID of this object.</value>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of this object.
|
||||
/// </summary>
|
||||
/// <value>The location of this object.</value>
|
||||
public Location Location { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the owner of this object.
|
||||
/// </summary>
|
||||
/// <value>The owner of this object.</value>
|
||||
public VirtualParadiseUser Owner { get; internal set; } = null!;
|
||||
|
||||
private protected VirtualParadiseClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given objects are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first object to compare.</param>
|
||||
/// <param name="right">The second object to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two objects are equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator ==(VirtualParadiseObject left, VirtualParadiseObject right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the two given objects are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first object to compare.</param>
|
||||
/// <param name="right">The second object to compare.</param>
|
||||
/// <returns><see langword="true" /> if the two objects are not equal; otherwise, <see langword="false" />.</returns>
|
||||
public static bool operator !=(VirtualParadiseObject left, VirtualParadiseObject right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a bump on this object.
|
||||
/// </summary>
|
||||
/// <param name="phase">
|
||||
/// The bump phase. If this value is <see langword="null" />, both <see cref="BumpPhase.Begin" /> and
|
||||
/// <see cref="BumpPhase.End" /> are sent in succession.
|
||||
/// </param>
|
||||
/// <param name="target">
|
||||
/// The target avatar to receive the event. If this value is <see langword="null" />, the bump will be broadcast to
|
||||
/// all avatars in the world.
|
||||
/// </param>
|
||||
public async Task BumpAsync(BumpPhase? phase = null, VirtualParadiseAvatar? target = null)
|
||||
{
|
||||
int session = target?.Session ?? 0;
|
||||
|
||||
ValueTask SendBegin()
|
||||
{
|
||||
lock (Client.Lock) vp_object_bump_begin(Client.NativeInstanceHandle, Id, session);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
ValueTask SendEnd()
|
||||
{
|
||||
lock (Client.Lock) vp_object_bump_end(Client.NativeInstanceHandle, Id, session);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
switch (phase)
|
||||
{
|
||||
case BumpPhase.Begin:
|
||||
await SendBegin();
|
||||
break;
|
||||
|
||||
case BumpPhase.End:
|
||||
await SendEnd();
|
||||
break;
|
||||
|
||||
case null:
|
||||
await SendBegin();
|
||||
await SendEnd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clicks the object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position at which to click the object.</param>
|
||||
/// <param name="target">
|
||||
/// The target avatar which will receive the event, or <see langword="null" /> to broadcast to every avatar.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="target" /> is the client's current avatar.</exception>
|
||||
public Task ClickAsync(Vector3d? position = null, VirtualParadiseAvatar? target = null)
|
||||
{
|
||||
if (target == Client.CurrentAvatar)
|
||||
ThrowHelper.ThrowCannotUseSelfException();
|
||||
|
||||
lock (Client.Lock)
|
||||
{
|
||||
int session = target?.Session ?? 0;
|
||||
(float x, float y, float z) = (Vector3) (position ?? Vector3d.Zero);
|
||||
|
||||
vp_object_click(Client.NativeInstanceHandle, Id, session, x, y, z);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes this object.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The client is not connected to a world.</exception>
|
||||
/// <exception cref="ObjectNotFoundException">The object does not exist.</exception>
|
||||
public Task DeleteAsync()
|
||||
{
|
||||
lock (Client.Lock)
|
||||
{
|
||||
var reason = (ReasonCode) vp_object_delete(Client.NativeInstanceHandle, Id);
|
||||
|
||||
switch (reason)
|
||||
{
|
||||
case ReasonCode.NotInWorld:
|
||||
return Task.FromException(ThrowHelper.NotInWorldException());
|
||||
|
||||
case ReasonCode.ObjectNotFound:
|
||||
return Task.FromException(ThrowHelper.ObjectNotFoundException());
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this object and another object are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The object to compare with this instance.</param>
|
||||
/// <returns><see langword="true" /> if the two objects are equal; otherwise, <see langword="false" />.</returns>
|
||||
public bool Equals(VirtualParadiseObject? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Location.World.Equals(other.Location.World) && Id == other.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((VirtualParadiseObject) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
return HashCode.Combine(Location.World, Id);
|
||||
}
|
||||
|
||||
protected internal virtual void ExtractFromOther(VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
Location = virtualParadiseObject.Location;
|
||||
Owner = virtualParadiseObject.Owner;
|
||||
}
|
||||
|
||||
protected internal virtual void ExtractFromInstance(IntPtr handle)
|
||||
{
|
||||
var data = Span<byte>.Empty;
|
||||
IntPtr dataPtr = vp_data(handle, DataAttribute.ObjectData, out int length);
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
data = new Span<byte>(dataPtr.ToPointer(), length);
|
||||
}
|
||||
}
|
||||
|
||||
ExtractFromData(data);
|
||||
}
|
||||
|
||||
protected virtual void ExtractFromData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
}
|
||||
}
|
19
VpSharp/src/Entities/VirtualParadiseObjectBuilder.cs
Normal file
19
VpSharp/src/Entities/VirtualParadiseObjectBuilder.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using VpSharp.Internal;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base class for object builders.
|
||||
/// </summary>
|
||||
public abstract class VirtualParadiseObjectBuilder
|
||||
{
|
||||
private protected VirtualParadiseObjectBuilder(VirtualParadiseClient client, ObjectBuilderMode mode)
|
||||
{
|
||||
Client = client;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
private protected VirtualParadiseClient Client { get; }
|
||||
|
||||
private protected ObjectBuilderMode Mode { get; }
|
||||
}
|
304
VpSharp/src/Entities/VirtualParadiseParticleEmitterObject.cs
Normal file
304
VpSharp/src/Entities/VirtualParadiseParticleEmitterObject.cs
Normal file
@ -0,0 +1,304 @@
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Internal.Attributes;
|
||||
using VpSharp.Internal.ValueConverters;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
public sealed class VirtualParadiseParticleEmitterObject : VirtualParadiseObject
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal VirtualParadiseParticleEmitterObject(VirtualParadiseClient client, int id)
|
||||
: base(client, id)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum acceleration.
|
||||
/// </summary>
|
||||
/// <value>The maximum acceleration.</value>
|
||||
[SerializationKey("acceleration_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d AccelerationMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum acceleration.
|
||||
/// </summary>
|
||||
/// <value>The minimum acceleration.</value>
|
||||
[SerializationKey("acceleration_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d AccelerationMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blend mode.
|
||||
/// </summary>
|
||||
/// <value>The blend mode.</value>
|
||||
[SerializationKey("blend")]
|
||||
[ValueConverter(typeof(StringToEnumConverter<ParticleBlendMode>))]
|
||||
public ParticleBlendMode BlendMode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum color.
|
||||
/// </summary>
|
||||
/// <value>The maximum color.</value>
|
||||
[SerializationKey("color_max")]
|
||||
[ValueConverter(typeof(HexToColorConverter))]
|
||||
public Color ColorMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum color.
|
||||
/// </summary>
|
||||
/// <value>The minimum color.</value>
|
||||
[SerializationKey("color_min")]
|
||||
[ValueConverter(typeof(HexToColorConverter))]
|
||||
public Color ColorMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the emitter lifespan.
|
||||
/// </summary>
|
||||
/// <value>The emitter lifespan.</value>
|
||||
[SerializationKey("emitter_lifespan")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan EmitterLifespan { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this emitter interpolates its values.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if this emitter interpolates its values; otherwise, <see langword="false" />.</value>
|
||||
[SerializationKey("interpolate")]
|
||||
[ValueConverter(typeof(IntToBoolConverter))]
|
||||
public bool Interpolate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opacity.
|
||||
/// </summary>
|
||||
/// <value>The opacity.</value>
|
||||
[SerializationKey("opacity")]
|
||||
public double Opacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the particle lifespan.
|
||||
/// </summary>
|
||||
/// <value>The particle lifespan.</value>
|
||||
[SerializationKey("particle_lifespan")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan ParticleLifespan { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the particle type.
|
||||
/// </summary>
|
||||
/// <value>The particle type.</value>
|
||||
[SerializationKey("particle_type")]
|
||||
[ValueConverter(typeof(StringToEnumConverter<ParticleType>))]
|
||||
public ParticleType ParticleType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the release count.
|
||||
/// </summary>
|
||||
/// <value>The release count.</value>
|
||||
[SerializationKey("release_count")]
|
||||
public int ReleaseCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the release time.
|
||||
/// </summary>
|
||||
/// <value>The release time.</value>
|
||||
[SerializationKey("release_time")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan ReleaseTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum size.
|
||||
/// </summary>
|
||||
/// <value>The maximum size.</value>
|
||||
[SerializationKey("size_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SizeMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum size.
|
||||
/// </summary>
|
||||
/// <value>The minimum size.</value>
|
||||
[SerializationKey("size_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SizeMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum speed.
|
||||
/// </summary>
|
||||
/// <value>The maximum speed.</value>
|
||||
[SerializationKey("speed_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SpeedMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum speed.
|
||||
/// </summary>
|
||||
/// <value>The minimum speed.</value>
|
||||
[SerializationKey("speed_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SpeedMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum spin.
|
||||
/// </summary>
|
||||
/// <value>The maximum spin.</value>
|
||||
[SerializationKey("spin_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SpinMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum spin.
|
||||
/// </summary>
|
||||
/// <value>The minimum spin.</value>
|
||||
[SerializationKey("spin_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d SpinMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum start angle.
|
||||
/// </summary>
|
||||
/// <value>The maximum start angle.</value>
|
||||
[SerializationKey("start_angle_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d StartAngleMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum start angle.
|
||||
/// </summary>
|
||||
/// <value>The minimum start angle.</value>
|
||||
[SerializationKey("start_angle_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d StartAngleMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum volume.
|
||||
/// </summary>
|
||||
/// <value>The maximum volume.</value>
|
||||
[SerializationKey("volume_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d VolumeMax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum volume.
|
||||
/// </summary>
|
||||
/// <value>The minimum volume.</value>
|
||||
[SerializationKey("volume_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d VolumeMin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tag.
|
||||
/// </summary>
|
||||
/// <value>The tag.</value>
|
||||
[SerializationKey("tag")]
|
||||
public string? Tag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture.
|
||||
/// </summary>
|
||||
/// <value>The texture.</value>
|
||||
[SerializationKey("texture")]
|
||||
public string? Texture { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void ExtractFromOther(VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
if (virtualParadiseObject is not VirtualParadiseParticleEmitterObject emitter)
|
||||
return;
|
||||
|
||||
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
PropertyInfo[] properties = typeof(VirtualParadiseParticleEmitterObject).GetProperties(bindingFlags);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<SerializationKeyAttribute>() is null)
|
||||
continue;
|
||||
|
||||
property.SetValue(this, property.GetValue(emitter));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExtractFromData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
#pragma warning disable 612
|
||||
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
PropertyInfo[] properties = typeof(VirtualParadiseParticleEmitterObject).GetProperties(bindingFlags);
|
||||
var keymap = new Dictionary<string, PropertyInfo>();
|
||||
var converterMap = new Dictionary<string, ValueConverter>();
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
var serializationKeyAttribute = property.GetCustomAttribute<SerializationKeyAttribute>();
|
||||
if (serializationKeyAttribute is null)
|
||||
continue;
|
||||
|
||||
keymap.Add(serializationKeyAttribute.Key, property);
|
||||
|
||||
var converterAttribute = property.GetCustomAttribute<ValueConverterAttribute>();
|
||||
if (converterAttribute is not null)
|
||||
{
|
||||
Type converterType = converterAttribute.ConverterType;
|
||||
if (Activator.CreateInstance(converterType) is ValueConverter converter)
|
||||
converterMap.Add(serializationKeyAttribute.Key, converter);
|
||||
}
|
||||
}
|
||||
#pragma warning restore 612
|
||||
|
||||
Span<char> text = stackalloc char[data.Length];
|
||||
Encoding.UTF8.GetChars(data, text);
|
||||
|
||||
using var keyBuffer = new Utf8ValueStringBuilder(false);
|
||||
using var valueBuffer = new Utf8ValueStringBuilder(false);
|
||||
var isKey = true;
|
||||
|
||||
for (var index = 0; index < text.Length; index++)
|
||||
{
|
||||
char current = text[index];
|
||||
switch (current)
|
||||
{
|
||||
case '=' when isKey:
|
||||
isKey = false;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
case '\r':
|
||||
var key = keyBuffer.ToString();
|
||||
var valueString = valueBuffer.ToString();
|
||||
object value = valueString;
|
||||
|
||||
if (keymap.TryGetValue(key, out PropertyInfo? property))
|
||||
{
|
||||
if (converterMap.TryGetValue(key, out ValueConverter? converter))
|
||||
{
|
||||
using var reader = new StringReader(valueString);
|
||||
converter.Deserialize(reader, out value);
|
||||
}
|
||||
else if (property.PropertyType != typeof(string))
|
||||
{
|
||||
value = Convert.ChangeType(value, property.PropertyType);
|
||||
}
|
||||
|
||||
property.SetValue(this, value);
|
||||
}
|
||||
|
||||
keyBuffer.Clear();
|
||||
valueBuffer.Clear();
|
||||
isKey = true;
|
||||
break;
|
||||
|
||||
case var _ when isKey:
|
||||
keyBuffer.Append(current);
|
||||
break;
|
||||
|
||||
case var _:
|
||||
valueBuffer.Append(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,476 @@
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
using System.Drawing;
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.Attributes;
|
||||
using VpSharp.Internal.ValueConverters;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides mutability for a <see cref="VirtualParadiseParticleEmitterObject" />.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseParticleEmitterObjectBuilder : VirtualParadiseObjectBuilder
|
||||
{
|
||||
internal VirtualParadiseParticleEmitterObjectBuilder(VirtualParadiseClient client, ObjectBuilderMode mode)
|
||||
: base(client, mode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum acceleration.
|
||||
/// </summary>
|
||||
/// <value>The maximum acceleration, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("acceleration_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? AccelerationMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum acceleration.
|
||||
/// </summary>
|
||||
/// <value>The minimum acceleration, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("acceleration_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? AccelerationMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the blend mode.
|
||||
/// </summary>
|
||||
/// <value>The blend mode, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("blend")]
|
||||
[ValueConverter(typeof(StringToEnumConverter<ParticleBlendMode>))]
|
||||
public ParticleBlendMode? BlendMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum color.
|
||||
/// </summary>
|
||||
/// <value>The maximum color, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("color_max")]
|
||||
[ValueConverter(typeof(HexToColorConverter))]
|
||||
public Color? ColorMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum color.
|
||||
/// </summary>
|
||||
/// <value>The minimum color, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("color_min")]
|
||||
[ValueConverter(typeof(HexToColorConverter))]
|
||||
public Color? ColorMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the emitter lifespan.
|
||||
/// </summary>
|
||||
/// <value>The emitter lifespan, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("emitter_lifespan")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan? EmitterLifespan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this emitter interpolates its values.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if this emitter interpolates its values; otherwise, <see langword="false" />, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("interpolate")]
|
||||
[ValueConverter(typeof(IntToBoolConverter))]
|
||||
public bool? Interpolate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the opacity.
|
||||
/// </summary>
|
||||
/// <value>The opacity, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("opacity")]
|
||||
public double? Opacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the particle lifespan.
|
||||
/// </summary>
|
||||
/// <value>The particle lifespan, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("particle_lifespan")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan? ParticleLifespan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the particle type.
|
||||
/// </summary>
|
||||
/// <value>The particle type, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("particle_type")]
|
||||
[ValueConverter(typeof(StringToEnumConverter<ParticleType>))]
|
||||
public ParticleType? ParticleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the release count.
|
||||
/// </summary>
|
||||
/// <value>The release count, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("release_count")]
|
||||
public int? ReleaseCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the release time.
|
||||
/// </summary>
|
||||
/// <value>The release time, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("release_time")]
|
||||
[ValueConverter(typeof(MillisecondToTimeSpanConverter))]
|
||||
public TimeSpan? ReleaseTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size.
|
||||
/// </summary>
|
||||
/// <value>The maximum size, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("size_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SizeMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum size.
|
||||
/// </summary>
|
||||
/// <value>The minimum size, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("size_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SizeMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum speed.
|
||||
/// </summary>
|
||||
/// <value>The maximum speed, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("speed_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SpeedMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum speed.
|
||||
/// </summary>
|
||||
/// <value>The minimum speed, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("speed_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SpeedMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum spin.
|
||||
/// </summary>
|
||||
/// <value>The maximum spin, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("spin_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SpinMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum spin.
|
||||
/// </summary>
|
||||
/// <value>The minimum spin, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("spin_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? SpinMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum start angle.
|
||||
/// </summary>
|
||||
/// <value>The maximum start angle, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("start_angle_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? StartAngleMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum start angle.
|
||||
/// </summary>
|
||||
/// <value>The minimum start angle, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("start_angle_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? StartAngleMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum volume.
|
||||
/// </summary>
|
||||
/// <value>The maximum volume, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("volume_max")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? VolumeMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum volume.
|
||||
/// </summary>
|
||||
/// <value>The minimum volume, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("volume_min")]
|
||||
[ValueConverter(typeof(Vector3dConverter))]
|
||||
public Vector3d? VolumeMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag.
|
||||
/// </summary>
|
||||
/// <value>The tag, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("tag")]
|
||||
public string Tag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the texture.
|
||||
/// </summary>
|
||||
/// <value>The texture, or <see langword="null" /> to leave unchanged.</value>
|
||||
[SerializationKey("texture")]
|
||||
public string Texture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum volume.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum volume, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithAccelerationMax(Vector3d? value)
|
||||
{
|
||||
AccelerationMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum acceleration.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum acceleration, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithAccelerationMin(Vector3d? value)
|
||||
{
|
||||
AccelerationMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the blend mode.
|
||||
/// </summary>
|
||||
/// <param name="value">The blend mode, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithBlendMode(ParticleBlendMode? value)
|
||||
{
|
||||
BlendMode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum color.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum color, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithColorMax(Color? value)
|
||||
{
|
||||
ColorMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum color.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum color, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithColorMin(Color? value)
|
||||
{
|
||||
ColorMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the emitter lifespan.
|
||||
/// </summary>
|
||||
/// <param name="value">The emitter lifespan, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithEmitterLifespan(TimeSpan? value)
|
||||
{
|
||||
EmitterLifespan = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the interpolation.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// <see langword="true" /> to enable interpolation, <see langword="false" /> to disable interpolation, or
|
||||
/// <see langword="null" /> to leave unchanged.
|
||||
/// </param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithInterpolation(bool? value)
|
||||
{
|
||||
Interpolate = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the opacity.
|
||||
/// </summary>
|
||||
/// <param name="value">The opacity, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithOpacity(double? value)
|
||||
{
|
||||
Opacity = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the particle lifespan.
|
||||
/// </summary>
|
||||
/// <param name="value">The particle lifespan, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithParticleLifespan(TimeSpan? value)
|
||||
{
|
||||
ParticleLifespan = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the particle type.
|
||||
/// </summary>
|
||||
/// <param name="value">The particle type, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithParticleType(ParticleType? value)
|
||||
{
|
||||
ParticleType = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the release count.
|
||||
/// </summary>
|
||||
/// <param name="value">The release count, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithReleaseCount(int? value)
|
||||
{
|
||||
ReleaseCount = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the release time.
|
||||
/// </summary>
|
||||
/// <param name="value">The release time, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithReleaseTime(TimeSpan? value)
|
||||
{
|
||||
ReleaseTime = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum size.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum size, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSizeMax(Vector3d? value)
|
||||
{
|
||||
SizeMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum size.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum size, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSizeMin(Vector3d? value)
|
||||
{
|
||||
SizeMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum speed.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum speed, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSpeedMax(Vector3d? value)
|
||||
{
|
||||
SpeedMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum speed.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum speed, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSpeedMin(Vector3d? value)
|
||||
{
|
||||
SpeedMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum spin.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum spin, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSpinMax(Vector3d? value)
|
||||
{
|
||||
SpinMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum spin.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum spin, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithSpinMin(Vector3d? value)
|
||||
{
|
||||
SpinMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum start angle.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum start angle, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithStartAngleMax(Vector3d? value)
|
||||
{
|
||||
StartAngleMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum start angle.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum start angle, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithStartAngleMin(Vector3d? value)
|
||||
{
|
||||
StartAngleMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum volume.
|
||||
/// </summary>
|
||||
/// <param name="value">The maximum volume, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithVolumeMax(Vector3d? value)
|
||||
{
|
||||
VolumeMax = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum volume.
|
||||
/// </summary>
|
||||
/// <param name="value">The minimum volume, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithVolumeMin(Vector3d? value)
|
||||
{
|
||||
VolumeMin = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tag.
|
||||
/// </summary>
|
||||
/// <param name="value">The tag, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithTag(string value)
|
||||
{
|
||||
Tag = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture.
|
||||
/// </summary>
|
||||
/// <param name="value">The texture, or <see langword="null" /> to leave unchanged.</param>
|
||||
/// <returns>The current instance.</returns>
|
||||
public VirtualParadiseParticleEmitterObjectBuilder WithTexture(string value)
|
||||
{
|
||||
Texture = value;
|
||||
return this;
|
||||
}
|
||||
}
|
45
VpSharp/src/Entities/VirtualParadisePath.cs
Normal file
45
VpSharp/src/Entities/VirtualParadisePath.cs
Normal file
@ -0,0 +1,45 @@
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path contained by a <see cref="VirtualParadisePathObject" />.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadisePath : ICloneable
|
||||
{
|
||||
internal VirtualParadisePath(PathEasing easing, string name, IEnumerable<PathPoint> points, bool isClosed)
|
||||
{
|
||||
Easing = easing;
|
||||
Name = name;
|
||||
IsClosed = isClosed;
|
||||
Points = points.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path type.
|
||||
/// </summary>
|
||||
/// <value>The path type.</value>
|
||||
public PathEasing Easing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this path is closed.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if this path is closed; otherwise, <see langword="false" />.</value>
|
||||
public bool IsClosed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this path.
|
||||
/// </summary>
|
||||
/// <value>The name of this path.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the points along this path.
|
||||
/// </summary>
|
||||
/// <value>The points along this path.</value>
|
||||
public IReadOnlyList<PathPoint> Points { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Clone()
|
||||
{
|
||||
return new VirtualParadisePath(Easing, Name, Points, IsClosed);
|
||||
}
|
||||
}
|
157
VpSharp/src/Entities/VirtualParadisePathObject.cs
Normal file
157
VpSharp/src/Entities/VirtualParadisePathObject.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path object.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadisePathObject : VirtualParadiseObject
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal VirtualParadisePathObject(VirtualParadiseClient client, int id)
|
||||
: base(client, id)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path in this object.
|
||||
/// </summary>
|
||||
/// <value>The path in this object.</value>
|
||||
public VirtualParadisePath Path { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void ExtractFromOther(VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
if (virtualParadiseObject is not VirtualParadisePathObject path)
|
||||
return;
|
||||
|
||||
Path = (VirtualParadisePath)path.Path.Clone();
|
||||
}
|
||||
|
||||
protected override void ExtractFromData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<char> chars = stackalloc char[data.Length];
|
||||
Encoding.UTF8.GetChars(data, chars);
|
||||
|
||||
using var buffer = new Utf8ValueStringBuilder(false);
|
||||
int index;
|
||||
|
||||
// version
|
||||
var version = 0;
|
||||
for (index = 0; index < chars.Length; index++)
|
||||
{
|
||||
if (chars[index] == '\n')
|
||||
{
|
||||
version = buffer.AsSpan().ToInt32();
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Append(chars[index]);
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
|
||||
if (version != 1)
|
||||
throw new NotSupportedException($"Unsupported path version {version}");
|
||||
|
||||
// path name
|
||||
var name = string.Empty;
|
||||
for (index += 1; index < chars.Length; index++)
|
||||
{
|
||||
if (chars[index] == '\n')
|
||||
{
|
||||
name = buffer.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Append(chars[index]);
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
|
||||
// type
|
||||
++index;
|
||||
int spaceIndex = chars[index..].IndexOf(' ');
|
||||
int newLineIndex = chars[index..].IndexOf('\n');
|
||||
|
||||
int pathType = int.Parse(chars[index..(index + spaceIndex)]);
|
||||
int closed = int.Parse(chars[(index + spaceIndex + 1)..(index + newLineIndex)]);
|
||||
index += newLineIndex;
|
||||
|
||||
// points from here onwards
|
||||
var points = new List<PathPoint>();
|
||||
TimeSpan? offset = null;
|
||||
double? x = null, y = null, z = null;
|
||||
float? rx = null, ry = null, rz = null, ra = null;
|
||||
|
||||
for (index += 1; index < chars.Length; index++)
|
||||
{
|
||||
if (char.IsWhiteSpace(chars[index]))
|
||||
{
|
||||
if (offset is null)
|
||||
{
|
||||
offset = TimeSpan.FromSeconds(buffer.AsSpan().ToDouble());
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (x is null)
|
||||
{
|
||||
x = buffer.AsSpan().ToDouble();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (y is null)
|
||||
{
|
||||
y = buffer.AsSpan().ToDouble();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (z is null)
|
||||
{
|
||||
z = buffer.AsSpan().ToDouble();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (rx is null)
|
||||
{
|
||||
rx = buffer.AsSpan().ToSingle();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (ry is null)
|
||||
{
|
||||
ry = buffer.AsSpan().ToSingle();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (rz is null)
|
||||
{
|
||||
rz = buffer.AsSpan().ToSingle();
|
||||
buffer.Clear();
|
||||
}
|
||||
else if (ra is null)
|
||||
{
|
||||
ra = buffer.AsSpan().ToSingle();
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (chars[index] == '\n' || index == chars.Length - 1)
|
||||
{
|
||||
var position = new Vector3d(x ?? 0, y ?? 0, z ?? 0);
|
||||
var axis = new Vector3(rx ?? 0, ry ?? 0, rz ?? 0);
|
||||
|
||||
Quaternion rotation = double.IsPositiveInfinity(ra ?? 0)
|
||||
? Quaternion.CreateFromYawPitchRoll(axis.Y, axis.X, axis.Z)
|
||||
: Quaternion.CreateFromAxisAngle(axis, ra ?? 0);
|
||||
|
||||
var point = new PathPoint(offset ?? TimeSpan.Zero, position, rotation);
|
||||
points.Add(point);
|
||||
|
||||
x = y = z = rx = ry = rz = ra = null;
|
||||
offset = null;
|
||||
}
|
||||
|
||||
buffer.Append(chars[index]);
|
||||
}
|
||||
|
||||
Path = new VirtualParadisePath((PathEasing)pathType, name, points, closed == 1);
|
||||
}
|
||||
}
|
239
VpSharp/src/Entities/VirtualParadiseUser.cs
Normal file
239
VpSharp/src/Entities/VirtualParadiseUser.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using System.Numerics;
|
||||
using VpSharp.Exceptions;
|
||||
using VpSharp.Extensions;
|
||||
using VpSharp.Internal;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
using static VpSharp.Internal.Native;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Virtual Paradise user.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseUser : IEquatable<VirtualParadiseUser>
|
||||
{
|
||||
private readonly VirtualParadiseClient _client;
|
||||
|
||||
internal VirtualParadiseUser(VirtualParadiseClient client, int id)
|
||||
{
|
||||
_client = client;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address of this user.
|
||||
/// </summary>
|
||||
/// <value>The email address of this user.</value>
|
||||
public string EmailAddress { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of this user.
|
||||
/// </summary>
|
||||
/// <value>The user's ID.</value>
|
||||
public int Id { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time at which this user was last online.
|
||||
/// </summary>
|
||||
/// <value>A <see cref="DateTimeOffset" /> representing the date and time this user was last online.</value>
|
||||
public DateTimeOffset LastLogin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this user.
|
||||
/// </summary>
|
||||
/// <value>The user's name.</value>
|
||||
public string Name { get; internal set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration for which this user has been online.
|
||||
/// </summary>
|
||||
/// <value>A <see cref="TimeSpan" /> representing the duration for which this user has been online.</value>
|
||||
public TimeSpan OnlineTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time at which this user was registered.
|
||||
/// </summary>
|
||||
/// <value>A <see cref="DateTimeOffset" /> representing the date and time this user was registered.</value>
|
||||
public DateTimeOffset RegistrationTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseUser" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator ==(VirtualParadiseUser? left, VirtualParadiseUser? right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseUser" /> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator !=(VirtualParadiseUser? left, VirtualParadiseUser? right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseUser" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if this instance is equal to <paramref name="other" />; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public bool Equals(VirtualParadiseUser? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is VirtualParadiseUser other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
return Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invites this user to a specified location.
|
||||
/// </summary>
|
||||
/// <param name="location">
|
||||
/// The invitation location. If <see langword="null" />, the client's current location is used.
|
||||
/// </param>
|
||||
public async Task<InviteResponse> InviteAsync(Location? location = null)
|
||||
{
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
if (_client.CurrentAvatar is not { } avatar)
|
||||
{
|
||||
ThrowHelper.ThrowNotInWorldException();
|
||||
return (InviteResponse) (-1);
|
||||
}
|
||||
|
||||
location ??= avatar.Location;
|
||||
TaskCompletionSource<ReasonCode> taskCompletionSource;
|
||||
|
||||
lock (_client.Lock)
|
||||
{
|
||||
int reference = ObjectReferenceCounter.GetNextReference();
|
||||
taskCompletionSource = _client.AddInviteCompletionSource(reference);
|
||||
|
||||
string world = location.Value.World.Name;
|
||||
(double x, double y, double z) = location.Value.Position;
|
||||
(double pitch, double yaw, _) = location.Value.Rotation.ToEulerAngles();
|
||||
|
||||
vp_int_set(_client.NativeInstanceHandle, IntegerAttribute.ReferenceNumber, reference);
|
||||
vp_invite(_client.NativeInstanceHandle, Id, world, x, y, z, (float) yaw, (float) pitch);
|
||||
}
|
||||
|
||||
ReasonCode reason = await taskCompletionSource.Task;
|
||||
return reason switch
|
||||
{
|
||||
ReasonCode.Success => InviteResponse.Accepted,
|
||||
ReasonCode.InviteDeclined => InviteResponse.Declined,
|
||||
ReasonCode.Timeout => InviteResponse.TimeOut,
|
||||
ReasonCode.NoSuchUser => throw new UserNotFoundException($"Cannot invite non-existent user {Id}."),
|
||||
var _ => throw new InvalidOperationException(
|
||||
$"An error occurred trying to invite the user: {reason:D} ({reason:G})")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a to join request to the user.
|
||||
/// </summary>
|
||||
/// <param name="suppressTeleport">
|
||||
/// If <see langword="true" />, the client's avatar will not teleport to the requested location automatically.
|
||||
/// Be careful, there is no way to retrieve
|
||||
/// </param>
|
||||
/// <returns>The result of the request.</returns>
|
||||
/// <exception cref="UserNotFoundException">This user is invalid and cannot be joined.</exception>
|
||||
/// <exception cref="InvalidOperationException">An unexpected error occurred trying to join the user.</exception>
|
||||
public async Task<JoinResult> JoinAsync(bool suppressTeleport = false)
|
||||
{
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
if (_client.CurrentAvatar is not { } avatar)
|
||||
{
|
||||
ThrowHelper.ThrowNotInWorldException();
|
||||
return default;
|
||||
}
|
||||
|
||||
// ReSharper disable InconsistentlySynchronizedField
|
||||
IntPtr handle = _client.NativeInstanceHandle;
|
||||
TaskCompletionSource<ReasonCode> taskCompletionSource;
|
||||
|
||||
lock (_client.Lock)
|
||||
{
|
||||
int reference = ObjectReferenceCounter.GetNextReference();
|
||||
vp_int_set(handle, IntegerAttribute.ReferenceNumber, reference);
|
||||
vp_join(handle, Id);
|
||||
|
||||
taskCompletionSource = _client.AddJoinCompletionSource(reference);
|
||||
}
|
||||
|
||||
ReasonCode reason = await taskCompletionSource.Task;
|
||||
Location? location = null;
|
||||
|
||||
if (reason == ReasonCode.Success)
|
||||
{
|
||||
string worldName;
|
||||
double x, y, z;
|
||||
float yaw, pitch;
|
||||
|
||||
lock (_client.Lock)
|
||||
{
|
||||
x = vp_double(handle, FloatAttribute.JoinX);
|
||||
y = vp_double(handle, FloatAttribute.JoinY);
|
||||
z = vp_double(handle, FloatAttribute.JoinZ);
|
||||
|
||||
yaw = (float) vp_double(handle, FloatAttribute.JoinYaw);
|
||||
pitch = (float) vp_double(handle, FloatAttribute.JoinPitch);
|
||||
|
||||
worldName = vp_string(handle, StringAttribute.JoinWorld);
|
||||
}
|
||||
|
||||
var position = new Vector3d(x, y, z);
|
||||
var rotation = Quaternion.CreateFromYawPitchRoll(yaw, pitch, 0);
|
||||
VirtualParadiseWorld world = (await _client.GetWorldAsync(worldName))!;
|
||||
|
||||
location = new Location(world, position, rotation);
|
||||
|
||||
if (!suppressTeleport)
|
||||
await avatar.TeleportAsync(location.Value);
|
||||
}
|
||||
|
||||
JoinResponse response = reason switch
|
||||
{
|
||||
ReasonCode.Success => JoinResponse.Accepted,
|
||||
ReasonCode.JoinDeclined => JoinResponse.Declined,
|
||||
ReasonCode.Timeout => JoinResponse.TimeOut,
|
||||
ReasonCode.NoSuchUser => throw new UserNotFoundException($"Cannot join non-existent user {Id}."),
|
||||
var _ => throw new InvalidOperationException(
|
||||
$"An error occurred trying to join the user: {reason:D} ({reason:G})")
|
||||
};
|
||||
// ReSharper enable InconsistentlySynchronizedField
|
||||
|
||||
return new JoinResult(response, location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"User [Id={Id}, Name={Name}]";
|
||||
}
|
||||
}
|
131
VpSharp/src/Entities/VirtualParadiseWorld.cs
Normal file
131
VpSharp/src/Entities/VirtualParadiseWorld.cs
Normal file
@ -0,0 +1,131 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace VpSharp.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a world in the Virtual Paradise universe.
|
||||
/// </summary>
|
||||
public sealed class VirtualParadiseWorld : IEquatable<VirtualParadiseWorld>
|
||||
{
|
||||
/// <summary>
|
||||
/// A world that represents no world in the universe.
|
||||
/// </summary>
|
||||
public static readonly VirtualParadiseWorld Nowhere = new(null!, "") {IsNowhere = true};
|
||||
|
||||
private readonly VirtualParadiseClient _client;
|
||||
|
||||
internal VirtualParadiseWorld(VirtualParadiseClient client, string name)
|
||||
{
|
||||
_client = client;
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of avatars currently in this world.
|
||||
/// </summary>
|
||||
/// <value>The number of avatars currently in this world.</value>
|
||||
public int AvatarCount { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this world.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings for this world.
|
||||
/// </summary>
|
||||
/// <value>The settings for this world.</value>
|
||||
public WorldSettings Settings { get; internal set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of this world.
|
||||
/// </summary>
|
||||
/// <value>The size of this world.</value>
|
||||
public Size Size { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of this world.
|
||||
/// </summary>
|
||||
/// <value>The state of this world.</value>
|
||||
public WorldState State { get; internal set; } = WorldState.Unknown;
|
||||
|
||||
internal bool IsNowhere { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseWorld" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator ==(VirtualParadiseWorld? left, VirtualParadiseWorld? right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseWorld" /> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator !=(VirtualParadiseWorld? left, VirtualParadiseWorld? right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="VirtualParadiseWorld" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if this instance is equal to <paramref name="other" />; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public bool Equals(VirtualParadiseWorld? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return IsNowhere == other.IsNowhere && Name == other.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is VirtualParadiseWorld other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
return HashCode.Combine(IsNowhere, Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the world settings globally.
|
||||
/// </summary>
|
||||
/// <param name="action">The builder which defines parameters to change.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="action" /> is <see langword="null" />.</exception>
|
||||
/// <exception cref="UnauthorizedAccessException">The client does not have permission to modify world settings.</exception>
|
||||
public async Task ModifyAsync(Action<WorldSettingsBuilder> action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
var builder = new WorldSettingsBuilder(_client);
|
||||
await Task.Run(() => action(builder));
|
||||
|
||||
builder.SendChanges();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"World [Name={Name}]";
|
||||
}
|
||||
}
|
40
VpSharp/src/EventData/AvatarClickedEventArgs.cs
Normal file
40
VpSharp/src/EventData/AvatarClickedEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarClicked" />.
|
||||
/// </summary>
|
||||
public sealed class AvatarClickedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvatarClickedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar responsible for the click.</param>
|
||||
/// <param name="clickedAvatar">The clicked avatar.</param>
|
||||
/// <param name="clickPoint">The click point.</param>
|
||||
public AvatarClickedEventArgs(VirtualParadiseAvatar avatar, VirtualParadiseAvatar clickedAvatar, Vector3d clickPoint)
|
||||
{
|
||||
Avatar = avatar;
|
||||
ClickedAvatar = clickedAvatar;
|
||||
ClickPoint = clickPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar responsible for the click.
|
||||
/// </summary>
|
||||
/// <value>The avatar responsible for the click.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clicked avatar.
|
||||
/// </summary>
|
||||
/// <value>The clicked avatar.</value>
|
||||
public VirtualParadiseAvatar ClickedAvatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point at which the avatar clicked the object.
|
||||
/// </summary>
|
||||
/// <value>The click point.</value>
|
||||
public Vector3d ClickPoint { get; }
|
||||
}
|
24
VpSharp/src/EventData/AvatarJoinedEventArgs.cs
Normal file
24
VpSharp/src/EventData/AvatarJoinedEventArgs.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarJoined" />.
|
||||
/// </summary>
|
||||
public sealed class AvatarJoinedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvatarJoinedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The newly-joined avatar.</param>
|
||||
public AvatarJoinedEventArgs(VirtualParadiseAvatar avatar)
|
||||
{
|
||||
Avatar = avatar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the newly-joined avatar.
|
||||
/// </summary>
|
||||
/// <value>The newly-joined avatar.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
}
|
24
VpSharp/src/EventData/AvatarLeftEventArgs.cs
Normal file
24
VpSharp/src/EventData/AvatarLeftEventArgs.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarLeft" />.
|
||||
/// </summary>
|
||||
public sealed class AvatarLeftEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvatarLeftEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The newly-departed avatar.</param>
|
||||
public AvatarLeftEventArgs(VirtualParadiseAvatar avatar)
|
||||
{
|
||||
Avatar = avatar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the newly-departed avatar.
|
||||
/// </summary>
|
||||
/// <value>The newly-departed avatar.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
}
|
40
VpSharp/src/EventData/AvatarMovedEventArgs.cs
Normal file
40
VpSharp/src/EventData/AvatarMovedEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarMoved" />.
|
||||
/// </summary>
|
||||
public sealed class AvatarMovedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvatarMovedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar whose type was changed.</param>
|
||||
/// <param name="locationAfter">The avatar's new location.</param>
|
||||
/// <param name="locationBefore">The avatar's old location.</param>
|
||||
public AvatarMovedEventArgs(VirtualParadiseAvatar avatar, Location locationAfter, Location? locationBefore)
|
||||
{
|
||||
Avatar = avatar;
|
||||
LocationAfter = locationAfter;
|
||||
LocationBefore = locationBefore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar whose location was changed.
|
||||
/// </summary>
|
||||
/// <value>The avatar whose location was changed.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar's location after the change.
|
||||
/// </summary>
|
||||
/// <value>The avatar's new location.</value>
|
||||
public Location LocationAfter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar's location before the change.
|
||||
/// </summary>
|
||||
/// <value>The avatar's old location.</value>
|
||||
public Location? LocationBefore { get; }
|
||||
}
|
40
VpSharp/src/EventData/AvatarTypeChangedEventArgs.cs
Normal file
40
VpSharp/src/EventData/AvatarTypeChangedEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.AvatarTypeChanged" />.
|
||||
/// </summary>
|
||||
public sealed class AvatarTypeChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvatarTypeChangedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar whose type was changed.</param>
|
||||
/// <param name="typeAfter">The avatar's new type.</param>
|
||||
/// <param name="typeBefore">The avatar's old type.</param>
|
||||
public AvatarTypeChangedEventArgs(VirtualParadiseAvatar avatar, int typeAfter, int? typeBefore)
|
||||
{
|
||||
Avatar = avatar;
|
||||
TypeAfter = typeAfter;
|
||||
TypeBefore = typeBefore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar whose type was changed.
|
||||
/// </summary>
|
||||
/// <value>The avatar whose type was changed.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar's type after the change.
|
||||
/// </summary>
|
||||
/// <value>The avatar's new type.</value>
|
||||
public int TypeAfter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar's type before the change.
|
||||
/// </summary>
|
||||
/// <value>The avatar's old type.</value>
|
||||
public int? TypeBefore { get; }
|
||||
}
|
23
VpSharp/src/EventData/DisconnectedEventArgs.cs
Normal file
23
VpSharp/src/EventData/DisconnectedEventArgs.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.WorldServerDisconnected" /> and
|
||||
/// <see cref="VirtualParadiseClient.UniverseServerDisconnected" />.
|
||||
/// </summary>
|
||||
public sealed class DisconnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisconnectedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason for the disconnect.</param>
|
||||
public DisconnectedEventArgs(DisconnectReason reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason for the disconnect.
|
||||
/// </summary>
|
||||
/// <value>The reason for the disconnect.</value>
|
||||
public DisconnectReason Reason { get; }
|
||||
}
|
22
VpSharp/src/EventData/InviteRequestReceivedEventArgs.cs
Normal file
22
VpSharp/src/EventData/InviteRequestReceivedEventArgs.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.InviteRequestReceived" />.
|
||||
/// </summary>
|
||||
public sealed class InviteRequestReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InviteRequestReceivedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="inviteRequest">The invite request.</param>
|
||||
public InviteRequestReceivedEventArgs(InviteRequest inviteRequest)
|
||||
{
|
||||
InviteRequest = inviteRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the invite request.
|
||||
/// </summary>
|
||||
/// <value>The invite request.</value>
|
||||
public InviteRequest InviteRequest { get; }
|
||||
}
|
22
VpSharp/src/EventData/JoinRequestReceivedEventArgs.cs
Normal file
22
VpSharp/src/EventData/JoinRequestReceivedEventArgs.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.JoinRequestReceived" />.
|
||||
/// </summary>
|
||||
public sealed class JoinRequestReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JoinRequestReceivedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="joinRequest">The join request.</param>
|
||||
public JoinRequestReceivedEventArgs(JoinRequest joinRequest)
|
||||
{
|
||||
JoinRequest = joinRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the join request.
|
||||
/// </summary>
|
||||
/// <value>The join request.</value>
|
||||
public JoinRequest JoinRequest { get; }
|
||||
}
|
24
VpSharp/src/EventData/MessageReceivedEventArgs.cs
Normal file
24
VpSharp/src/EventData/MessageReceivedEventArgs.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.MessageReceived" />.
|
||||
/// </summary>
|
||||
public sealed class MessageReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageReceivedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The received message.</param>
|
||||
public MessageReceivedEventArgs(VirtualParadiseMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message.
|
||||
/// </summary>
|
||||
/// <value>The message.</value>
|
||||
public VirtualParadiseMessage Message { get; }
|
||||
}
|
40
VpSharp/src/EventData/ObjectBumpEventArgs.cs
Normal file
40
VpSharp/src/EventData/ObjectBumpEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.ObjectBump" />.
|
||||
/// </summary>
|
||||
public sealed class ObjectBumpEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectBumpEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar.</param>
|
||||
/// <param name="virtualParadiseObject">The object.</param>
|
||||
/// <param name="phase">The bump phase.</param>
|
||||
public ObjectBumpEventArgs(VirtualParadiseAvatar avatar, VirtualParadiseObject virtualParadiseObject, BumpPhase phase)
|
||||
{
|
||||
Avatar = avatar;
|
||||
Object = virtualParadiseObject;
|
||||
Phase = phase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar responsible for the bump.
|
||||
/// </summary>
|
||||
/// <value>The avatar responsible for the bump.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object to which this event pertains.
|
||||
/// </summary>
|
||||
/// <value>The object to which this event pertains.</value>
|
||||
public VirtualParadiseObject Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bump phase.
|
||||
/// </summary>
|
||||
/// <value>The bump phase.</value>
|
||||
public BumpPhase Phase { get; }
|
||||
}
|
47
VpSharp/src/EventData/ObjectChangedEventArgs.cs
Normal file
47
VpSharp/src/EventData/ObjectChangedEventArgs.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.ObjectChanged" />.
|
||||
/// </summary>
|
||||
public sealed class ObjectChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectChangedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar which changed the object.</param>
|
||||
/// <param name="objectBefore">The state of the object prior to the change.</param>
|
||||
/// <param name="virtualParadiseObject">The object which was changed, containing updated values.</param>
|
||||
public ObjectChangedEventArgs(
|
||||
VirtualParadiseAvatar avatar,
|
||||
VirtualParadiseObject objectBefore,
|
||||
VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
Avatar = avatar;
|
||||
Object = virtualParadiseObject;
|
||||
ObjectBefore = objectBefore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar which changed the object.
|
||||
/// </summary>
|
||||
/// <value>The avatar which changed the object.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object which was changed.
|
||||
/// </summary>
|
||||
/// <value>The object which was changed.</value>
|
||||
/// <remarks>This instance will contain the updated values of the object.</remarks>
|
||||
public VirtualParadiseObject Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the object prior to the change.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The state of the object prior to the change. This value may be <see langword="null" /> if the client did not
|
||||
/// previously have the object cached.
|
||||
/// </value>
|
||||
public VirtualParadiseObject ObjectBefore { get; }
|
||||
}
|
43
VpSharp/src/EventData/ObjectClickedEventArgs.cs
Normal file
43
VpSharp/src/EventData/ObjectClickedEventArgs.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.ObjectClicked" />.
|
||||
/// </summary>
|
||||
public sealed class ObjectClickedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectClickedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar responsible for the click.</param>
|
||||
/// <param name="virtualParadiseObject">The clicked object.</param>
|
||||
/// <param name="clickPoint">The click point.</param>
|
||||
public ObjectClickedEventArgs(
|
||||
VirtualParadiseAvatar avatar,
|
||||
VirtualParadiseObject virtualParadiseObject,
|
||||
Vector3d clickPoint)
|
||||
{
|
||||
Avatar = avatar;
|
||||
Object = virtualParadiseObject;
|
||||
ClickPoint = clickPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar responsible for the click.
|
||||
/// </summary>
|
||||
/// <value>The avatar responsible for the click.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point at which the avatar clicked the object.
|
||||
/// </summary>
|
||||
/// <value>The click point.</value>
|
||||
public Vector3d ClickPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clicked object.
|
||||
/// </summary>
|
||||
/// <value>The clicked object.</value>
|
||||
public VirtualParadiseObject Object { get; }
|
||||
}
|
32
VpSharp/src/EventData/ObjectCreatedEventArgs.cs
Normal file
32
VpSharp/src/EventData/ObjectCreatedEventArgs.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.ObjectCreated" />.
|
||||
/// </summary>
|
||||
public sealed class ObjectCreatedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectClickedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar responsible for the object being created.</param>
|
||||
/// <param name="virtualParadiseObject">The created object.</param>
|
||||
public ObjectCreatedEventArgs(VirtualParadiseAvatar avatar, VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
Avatar = avatar;
|
||||
Object = virtualParadiseObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar responsible for the object being created.
|
||||
/// </summary>
|
||||
/// <value>The avatar responsible for the object being created.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object which was created.
|
||||
/// </summary>
|
||||
/// <value>The object which was created.</value>
|
||||
public VirtualParadiseObject Object { get; }
|
||||
}
|
49
VpSharp/src/EventData/ObjectDeletedEventArgs.cs
Normal file
49
VpSharp/src/EventData/ObjectDeletedEventArgs.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.ObjectDeleted" />.
|
||||
/// </summary>
|
||||
public sealed class ObjectDeletedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectDeletedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar responsible for the object being deleted.</param>
|
||||
/// <param name="objectId">The ID of the deleted object.</param>
|
||||
/// <param name="virtualParadiseObject">The deleted object.</param>
|
||||
public ObjectDeletedEventArgs(VirtualParadiseAvatar avatar, int objectId, VirtualParadiseObject virtualParadiseObject)
|
||||
{
|
||||
Avatar = avatar;
|
||||
ObjectId = objectId;
|
||||
Object = virtualParadiseObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar responsible for the object being deleted.
|
||||
/// </summary>
|
||||
/// <value>The avatar responsible for the object being deleted.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object which was deleted.
|
||||
/// </summary>
|
||||
/// <value>The object which was deleted.</value>
|
||||
/// <remarks>
|
||||
/// This value may be <see langword="null" /> if the client did not have the object cached prior to the deletion.
|
||||
/// <see cref="ObjectId" /> will always be assigned.
|
||||
/// </remarks>
|
||||
/// <seealso cref="ObjectId" />
|
||||
public VirtualParadiseObject Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the object which was deleted.
|
||||
/// </summary>
|
||||
/// <value>The ID of the object which was deleted.</value>
|
||||
/// <remarks>
|
||||
/// This value will always be assigned, regardless of whether or not the client had previously cached the object.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Object" />
|
||||
public int ObjectId { get; }
|
||||
}
|
32
VpSharp/src/EventData/TeleportedEventArgs.cs
Normal file
32
VpSharp/src/EventData/TeleportedEventArgs.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event arguments for <see cref="VirtualParadiseClient.Teleported" />.
|
||||
/// </summary>
|
||||
public sealed class TeleportedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TeleportedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar which initiated the teleport.</param>
|
||||
/// <param name="location">The target location of the teleport.</param>
|
||||
public TeleportedEventArgs(VirtualParadiseAvatar avatar, Location location)
|
||||
{
|
||||
Avatar = avatar;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar which initiated the teleport.
|
||||
/// </summary>
|
||||
/// <value>The avatar which initiated the teleport.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target location of the teleport.
|
||||
/// </summary>
|
||||
/// <value>The target location of the teleport.</value>
|
||||
public Location Location { get; }
|
||||
}
|
40
VpSharp/src/EventData/UriReceivedEventArgs.cs
Normal file
40
VpSharp/src/EventData/UriReceivedEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp.EventData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides event data for <see cref="VirtualParadiseClient.UriReceived" />.
|
||||
/// </summary>
|
||||
public sealed class UriReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UriReceivedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="uri">The received URI.</param>
|
||||
/// <param name="target">The URI target.</param>
|
||||
/// <param name="avatar">The avatar who sent the URI.</param>
|
||||
public UriReceivedEventArgs(Uri uri, UriTarget target, VirtualParadiseAvatar avatar)
|
||||
{
|
||||
Uri = uri;
|
||||
Target = target;
|
||||
Avatar = avatar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar who sent the URI.
|
||||
/// </summary>
|
||||
/// <value>The avatar who sent the URI.</value>
|
||||
public VirtualParadiseAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URI target.
|
||||
/// </summary>
|
||||
/// <value>A value from the <see cref="UriTarget" /> enum.</value>
|
||||
public UriTarget Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URI which was received.
|
||||
/// </summary>
|
||||
/// <value>The received URI.</value>
|
||||
public Uri Uri { get; }
|
||||
}
|
24
VpSharp/src/Exceptions/ObjectNotFoundException.cs
Normal file
24
VpSharp/src/Exceptions/ObjectNotFoundException.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace VpSharp.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an operation was performed on an object that does not exist.
|
||||
/// </summary>
|
||||
public sealed class ObjectNotFoundException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ObjectNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ObjectNotFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ObjectNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
16
VpSharp/src/Exceptions/UserNotFoundException.cs
Normal file
16
VpSharp/src/Exceptions/UserNotFoundException.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace VpSharp.Exceptions;
|
||||
|
||||
public sealed class UserNotFoundException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public UserNotFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public UserNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
25
VpSharp/src/Exceptions/VersionMismatchException.cs
Normal file
25
VpSharp/src/Exceptions/VersionMismatchException.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace VpSharp.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when the version of the .NET wrapper and the version of the native SDK do not match.
|
||||
/// </summary>
|
||||
public sealed class VersionMismatchException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public VersionMismatchException()
|
||||
: this("The version of the .NET wrapper and the version of the native SDK do not match.")
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VersionMismatchException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VersionMismatchException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
19
VpSharp/src/Exceptions/WorldNotFoundException.cs
Normal file
19
VpSharp/src/Exceptions/WorldNotFoundException.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace VpSharp.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an attempt to access a world does not exist.
|
||||
/// </summary>
|
||||
public sealed class WorldNotFoundException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public WorldNotFoundException(string worldName)
|
||||
: base($"No world with the name {worldName} was found.")
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorldNotFoundException(string worldName, Exception innerException)
|
||||
: base($"No world with the name {worldName} was found.", innerException)
|
||||
{
|
||||
}
|
||||
}
|
34
VpSharp/src/Extensions/ColorExtensions.cs
Normal file
34
VpSharp/src/Extensions/ColorExtensions.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace VpSharp.Extensions;
|
||||
|
||||
internal static class ColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Deconstructs this color into RGB components.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to deconstruct.</param>
|
||||
/// <param name="r">The red component.</param>
|
||||
/// <param name="g">The green component.</param>
|
||||
/// <param name="b">The blue component.</param>
|
||||
public static void Deconstruct(this Color color, out byte r, out byte g, out byte b)
|
||||
{
|
||||
r = color.R;
|
||||
g = color.G;
|
||||
b = color.B;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructs this color into ARGB components.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to deconstruct.</param>
|
||||
/// <param name="a">The alpha component.</param>
|
||||
/// <param name="r">The red component.</param>
|
||||
/// <param name="g">The green component.</param>
|
||||
/// <param name="b">The blue component.</param>
|
||||
public static void Deconstruct(this Color color, out byte a, out byte r, out byte g, out byte b)
|
||||
{
|
||||
(r, g, b) = color;
|
||||
a = color.A;
|
||||
}
|
||||
}
|
78
VpSharp/src/Extensions/QuaternionExtensions.cs
Normal file
78
VpSharp/src/Extensions/QuaternionExtensions.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace VpSharp.Extensions;
|
||||
|
||||
public static class QuaternionExtensions
|
||||
{
|
||||
// https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
|
||||
public static Vector3d ToEulerAngles(this Quaternion value, bool radians = true)
|
||||
{
|
||||
double a = 2.0 * value.Y * value.W - 2.0 * value.X * value.Z;
|
||||
double b = 1.0 - 2.0 * value.Y * value.Y - 2.0 * value.Z * value.Z;
|
||||
double y = -Math.Atan2(a, b);
|
||||
|
||||
a = 2.0 * value.X * value.Y;
|
||||
b = 2.0 * value.Z * value.W;
|
||||
double z = Math.Asin(a + b);
|
||||
|
||||
a = 2.0 * value.X * value.W - 2.0 * value.Y * value.Z;
|
||||
b = 1.0 - 2.0 * value.X * value.X - 2.0 * value.Z * value.Z;
|
||||
double x = Math.Atan2(a, b);
|
||||
|
||||
if (!radians)
|
||||
{
|
||||
x = (180.0 / Math.PI) * x;
|
||||
y = (180.0 / Math.PI) * y;
|
||||
z = (180.0 / Math.PI) * z;
|
||||
}
|
||||
|
||||
return new Vector3d(x, y, z);
|
||||
}
|
||||
|
||||
public static Vector3d ToEulerAnglesF(this Quaternion value, bool radians = true)
|
||||
{
|
||||
float a = 2.0f * value.Y * value.W - 2.0f * value.X * value.Z;
|
||||
float b = 1.0f - 2.0f * value.Y * value.Y - 2.0f * value.Z * value.Z;
|
||||
float y = -MathF.Atan2(a, b);
|
||||
|
||||
a = 2.0f * value.X * value.Y;
|
||||
b = 2.0f * value.Z * value.W;
|
||||
float z = MathF.Asin(a + b);
|
||||
|
||||
a = 2.0f * value.X * value.W - 2.0f * value.Y * value.Z;
|
||||
b = 1.0f - 2.0f * value.X * value.X - 2.0f * value.Z * value.Z;
|
||||
float x = MathF.Atan2(a, b);
|
||||
|
||||
if (!radians)
|
||||
{
|
||||
x = (180.0f / MathF.PI) * x;
|
||||
y = (180.0f / MathF.PI) * y;
|
||||
z = (180.0f / MathF.PI) * z;
|
||||
}
|
||||
|
||||
return new Vector3d(x, y, z);
|
||||
}
|
||||
|
||||
// https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
|
||||
public static void ToAxisAngle(this Quaternion value, out Vector3 axis, out float angle)
|
||||
{
|
||||
angle = 2.0f * MathF.Acos(value.W);
|
||||
|
||||
float x = value.X / MathF.Sqrt(1.0f - value.W * value.W);
|
||||
float y = value.Y / MathF.Sqrt(1.0f - value.W * value.W);
|
||||
float z = value.Z / MathF.Sqrt(1.0f - value.W * value.W);
|
||||
|
||||
axis = new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
public static void ToAxisAngle(this Quaternion value, out Vector3d axis, out double angle)
|
||||
{
|
||||
angle = 2.0 * Math.Acos(value.W);
|
||||
|
||||
double x = value.X / Math.Sqrt(1.0 - value.W * value.W);
|
||||
double y = value.Y / Math.Sqrt(1.0 - value.W * value.W);
|
||||
double z = value.Z / Math.Sqrt(1.0 - value.W * value.W);
|
||||
|
||||
axis = new Vector3d(x, y, z);
|
||||
}
|
||||
}
|
233
VpSharp/src/Extensions/SpanExtensions.cs
Normal file
233
VpSharp/src/Extensions/SpanExtensions.cs
Normal file
@ -0,0 +1,233 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Cysharp.Text;
|
||||
|
||||
namespace VpSharp.Extensions;
|
||||
|
||||
internal static class SpanExtensions
|
||||
{
|
||||
public static double ToDouble(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return double.Parse(chars.Trim());
|
||||
}
|
||||
|
||||
public static int ToInt32(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return int.Parse(chars.Trim());
|
||||
}
|
||||
|
||||
public static float ToSingle(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return float.Parse(chars.Trim());
|
||||
}
|
||||
|
||||
public static Vector2 ToVector2(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return ToVector2(chars.Trim());
|
||||
}
|
||||
|
||||
public static Vector3 ToVector3(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return ToVector3(chars.Trim());
|
||||
}
|
||||
|
||||
public static Vector3d ToVector3d(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return ToVector3d(chars.Trim());
|
||||
}
|
||||
|
||||
public static Vector4 ToVector4(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
Span<char> chars = stackalloc char[value.Length];
|
||||
Encoding.UTF8.GetChars(value, chars);
|
||||
return ToVector4(chars.Trim());
|
||||
}
|
||||
|
||||
public static Vector2 ToVector2(this ReadOnlySpan<char> value)
|
||||
{
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
byte spaceCount = 0;
|
||||
|
||||
using var buffer = new Utf8ValueStringBuilder(false);
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
{
|
||||
char current = value[index];
|
||||
|
||||
if (char.IsDigit(current) || current is '.' or '-')
|
||||
{
|
||||
buffer.Append(current);
|
||||
if (index < value.Length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current != ' ')
|
||||
continue;
|
||||
|
||||
ReadOnlySpan<byte> span = buffer.AsSpan();
|
||||
var floatValue = span.ToSingle();
|
||||
|
||||
switch (++spaceCount)
|
||||
{
|
||||
case 1:
|
||||
x = floatValue;
|
||||
break;
|
||||
case 2:
|
||||
y = floatValue;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
}
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
public static Vector3 ToVector3(this ReadOnlySpan<char> value)
|
||||
{
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float z = 0;
|
||||
byte spaceCount = 0;
|
||||
|
||||
using var buffer = new Utf8ValueStringBuilder(false);
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
{
|
||||
char current = value[index];
|
||||
|
||||
if (char.IsDigit(current) || current is '.' or '-')
|
||||
{
|
||||
buffer.Append(current);
|
||||
if (index < value.Length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current != ' ')
|
||||
continue;
|
||||
|
||||
ReadOnlySpan<byte> span = buffer.AsSpan();
|
||||
var floatValue = span.ToSingle();
|
||||
|
||||
switch (++spaceCount)
|
||||
{
|
||||
case 1:
|
||||
x = floatValue;
|
||||
break;
|
||||
case 2:
|
||||
y = floatValue;
|
||||
break;
|
||||
case 3:
|
||||
z = floatValue;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
}
|
||||
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
public static Vector3d ToVector3d(this ReadOnlySpan<char> value)
|
||||
{
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
double z = 0;
|
||||
byte spaceCount = 0;
|
||||
|
||||
using var buffer = new Utf8ValueStringBuilder(false);
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
{
|
||||
char current = value[index];
|
||||
|
||||
if (char.IsDigit(current) || current is '.' or '-')
|
||||
{
|
||||
buffer.Append(current);
|
||||
if (index < value.Length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current != ' ')
|
||||
continue;
|
||||
|
||||
ReadOnlySpan<byte> span = buffer.AsSpan();
|
||||
var floatValue = span.ToDouble();
|
||||
|
||||
switch (++spaceCount)
|
||||
{
|
||||
case 1:
|
||||
x = floatValue;
|
||||
break;
|
||||
case 2:
|
||||
y = floatValue;
|
||||
break;
|
||||
case 3:
|
||||
z = floatValue;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
}
|
||||
|
||||
return new Vector3d(x, y, z);
|
||||
}
|
||||
|
||||
public static Vector4 ToVector4(this ReadOnlySpan<char> value)
|
||||
{
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float z = 0;
|
||||
float w = 0;
|
||||
byte spaceCount = 0;
|
||||
|
||||
using var buffer = new Utf8ValueStringBuilder(false);
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
{
|
||||
char current = value[index];
|
||||
|
||||
if (char.IsDigit(current) || current is '.' or '-')
|
||||
{
|
||||
buffer.Append(current);
|
||||
if (index < value.Length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current != ' ')
|
||||
continue;
|
||||
|
||||
ReadOnlySpan<byte> span = buffer.AsSpan();
|
||||
var floatValue = span.ToSingle();
|
||||
|
||||
switch (++spaceCount)
|
||||
{
|
||||
case 1:
|
||||
x = floatValue;
|
||||
break;
|
||||
case 2:
|
||||
y = floatValue;
|
||||
break;
|
||||
case 3:
|
||||
z = floatValue;
|
||||
break;
|
||||
case 4:
|
||||
w = floatValue;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
}
|
||||
|
||||
return new Vector4(x, y, z, w);
|
||||
}
|
||||
}
|
36
VpSharp/src/Extensions/VectorExtensions.cs
Normal file
36
VpSharp/src/Extensions/VectorExtensions.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace VpSharp.Extensions;
|
||||
|
||||
public static class VectorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Deconstructs this vector.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to deconstruct.</param>
|
||||
/// <param name="x">The X component value.</param>
|
||||
/// <param name="y">The Y component value.</param>
|
||||
/// <param name="z">The Z component value.</param>
|
||||
public static void Deconstruct(this Vector3 vector, out float x, out float y, out float z)
|
||||
{
|
||||
x = vector.X;
|
||||
y = vector.Y;
|
||||
z = vector.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructs this vector.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to deconstruct.</param>
|
||||
/// <param name="x">The X component value.</param>
|
||||
/// <param name="y">The Y component value.</param>
|
||||
/// <param name="z">The Z component value.</param>
|
||||
/// <param name="w">The W component value.</param>
|
||||
public static void Deconstruct(this Vector4 vector, out float x, out float y, out float z, out float w)
|
||||
{
|
||||
x = vector.X;
|
||||
y = vector.Y;
|
||||
z = vector.Z;
|
||||
w = vector.W;
|
||||
}
|
||||
}
|
14
VpSharp/src/FogMode.cs
Normal file
14
VpSharp/src/FogMode.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of fog modes.
|
||||
/// </summary>
|
||||
public enum FogMode
|
||||
{
|
||||
None,
|
||||
Linear,
|
||||
Exp,
|
||||
Exponential = Exp,
|
||||
Exp2,
|
||||
Exponential2 = Exp2
|
||||
}
|
21
VpSharp/src/Internal/Attributes/SerializationKeyAttribute.cs
Normal file
21
VpSharp/src/Internal/Attributes/SerializationKeyAttribute.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace VpSharp.Internal.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
internal sealed class SerializationKeyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SerializationKeyAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key name.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key" /> is <see langword="null" />.</exception>
|
||||
public SerializationKeyAttribute(string key)
|
||||
{
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the key.
|
||||
/// </summary>
|
||||
/// <value>The value of the key.</value>
|
||||
public string Key { get; }
|
||||
}
|
50
VpSharp/src/Internal/Attributes/ValueConverterAttribute.cs
Normal file
50
VpSharp/src/Internal/Attributes/ValueConverterAttribute.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using VpSharp.Internal.ValueConverters;
|
||||
|
||||
#pragma warning disable 612
|
||||
namespace VpSharp.Internal.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
|
||||
internal sealed class ValueConverterAttribute : Attribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ValueConverterAttribute(Type converterType)
|
||||
: this(converterType, null)
|
||||
{
|
||||
UseArgs = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueConverterAttribute(Type converterType, params object?[]? args)
|
||||
{
|
||||
if (converterType is null)
|
||||
throw new ArgumentNullException(nameof(converterType));
|
||||
|
||||
if (converterType.IsAbstract)
|
||||
throw new ArgumentException("Cannot use abstract converter.");
|
||||
|
||||
if (!converterType.IsSubclassOf(typeof(ValueConverter)))
|
||||
throw new ArgumentException($"Converter does not inherit {typeof(ValueConverter)}");
|
||||
|
||||
ConverterType = converterType;
|
||||
Args = args;
|
||||
UseArgs = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the converter type.
|
||||
/// </summary>
|
||||
/// <value>The converter type.</value>
|
||||
public Type ConverterType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments to pass to the constructor, if any.
|
||||
/// </summary>
|
||||
/// <value>Arguments to pass to the constructor.</value>
|
||||
public object?[]? Args { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether arguments were supplied.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if arguments were supplied; otherwise, <see langword="false" />.</value>
|
||||
public bool UseArgs { get; }
|
||||
}
|
230
VpSharp/src/Internal/Connection.cs
Normal file
230
VpSharp/src/Internal/Connection.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using VpSharp.NativeApi;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal class Connection
|
||||
{
|
||||
private readonly object _lockObject;
|
||||
private readonly Socket _socket;
|
||||
|
||||
private byte[] _pendingBuffer;
|
||||
private readonly List<byte[]> _readyBuffers = new();
|
||||
private Timer _timer;
|
||||
private IntPtr _vpConnection;
|
||||
|
||||
public Connection(IntPtr vpConnection, object lockObject)
|
||||
{
|
||||
_vpConnection = vpConnection;
|
||||
_lockObject = lockObject;
|
||||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
|
||||
public void BeforeDestroy()
|
||||
{
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
_vpConnection = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public int Connect(string host, ushort port)
|
||||
{
|
||||
_socket.BeginConnect(host, port, ConnectCallback, this);
|
||||
return (int) NetworkReturnCode.Success;
|
||||
}
|
||||
|
||||
private static void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
var connection = ar.AsyncState as Connection;
|
||||
try
|
||||
{
|
||||
if (connection is not null)
|
||||
{
|
||||
connection._socket.EndConnect(ar);
|
||||
|
||||
connection._pendingBuffer = new byte[1024];
|
||||
connection._socket.BeginReceive(connection._pendingBuffer, 0, 1024, SocketFlags.None, ReceiveCallback,
|
||||
connection);
|
||||
|
||||
connection.Notify(NetworkNotification.Connect, (int) NetworkReturnCode.Success);
|
||||
}
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
connection?.Notify(NetworkNotification.Connect, (int) NetworkReturnCode.ConnectionError);
|
||||
}
|
||||
}
|
||||
|
||||
public static int ConnectNative(IntPtr ptr, IntPtr hostPtr, ushort port)
|
||||
{
|
||||
GCHandle handle = GCHandle.FromIntPtr(ptr);
|
||||
var connection = handle.Target as Connection;
|
||||
string host = Marshal.PtrToStringAnsi(hostPtr);
|
||||
if (connection is not null) return connection.Connect(host, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static IntPtr CreateNative(IntPtr vpConnection, IntPtr context)
|
||||
{
|
||||
GCHandle contextHandle = GCHandle.FromIntPtr(context);
|
||||
var connection = new Connection(vpConnection, contextHandle.Target);
|
||||
GCHandle handle = GCHandle.Alloc(connection, GCHandleType.Normal);
|
||||
var ptr = GCHandle.ToIntPtr(handle);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public static void DestroyNative(IntPtr ptr)
|
||||
{
|
||||
GCHandle handle = GCHandle.FromIntPtr(ptr);
|
||||
var connection = handle.Target as Connection;
|
||||
connection?.BeforeDestroy();
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
private void HandleTimeout()
|
||||
{
|
||||
if (_timer != null) Notify(NetworkNotification.Timeout, 0);
|
||||
}
|
||||
|
||||
private static void HandleTimeout(object state)
|
||||
{
|
||||
(state as Connection)?.HandleTimeout();
|
||||
}
|
||||
|
||||
private void Notify(NetworkNotification notification, int rc)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_vpConnection != IntPtr.Zero)
|
||||
Native.vp_net_notify(_vpConnection, (int) notification, rc);
|
||||
}
|
||||
}
|
||||
|
||||
public int Receive(IntPtr data, uint length)
|
||||
{
|
||||
if (_readyBuffers.Count == 0) return (int) NetworkReturnCode.WouldBlock;
|
||||
|
||||
var spaceLeft = (int) length;
|
||||
IntPtr destination = data;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < _readyBuffers.Count && spaceLeft > 0; ++i)
|
||||
{
|
||||
byte[] source = _readyBuffers[i];
|
||||
if (source.Length > spaceLeft)
|
||||
{
|
||||
Marshal.Copy(source, 0, data, spaceLeft);
|
||||
|
||||
_readyBuffers[i] = new byte[source.Length - spaceLeft];
|
||||
Array.Copy(source, spaceLeft, _readyBuffers[i], 0, source.Length - spaceLeft);
|
||||
|
||||
spaceLeft = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Marshal.Copy(source, 0, destination, source.Length);
|
||||
destination += source.Length;
|
||||
spaceLeft -= source.Length;
|
||||
}
|
||||
|
||||
_readyBuffers.RemoveRange(0, i);
|
||||
|
||||
return (int) (length - spaceLeft);
|
||||
}
|
||||
|
||||
private static void ReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
if (ar.AsyncState is not Connection connection) return;
|
||||
int bytesRead;
|
||||
|
||||
try
|
||||
{
|
||||
bytesRead = connection._socket.EndReceive(ar);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
connection.Notify(NetworkNotification.Disconnect, e.ErrorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead < connection._pendingBuffer.Length)
|
||||
{
|
||||
var buffer = new byte[bytesRead];
|
||||
Array.Copy(connection._pendingBuffer, buffer, bytesRead);
|
||||
connection._readyBuffers.Add(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
connection._readyBuffers.Add(connection._pendingBuffer);
|
||||
connection._pendingBuffer = new byte[1024];
|
||||
}
|
||||
|
||||
if (connection._vpConnection != IntPtr.Zero)
|
||||
{
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
connection.Notify(NetworkNotification.ReadReady, 0);
|
||||
|
||||
try
|
||||
{
|
||||
connection._socket.BeginReceive(connection._pendingBuffer, 0, 1024, SocketFlags.None, ReceiveCallback,
|
||||
connection);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
connection.Notify(NetworkNotification.Disconnect, e.ErrorCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
connection.Notify(NetworkNotification.Disconnect, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static int ReceiveNative(IntPtr ptr, IntPtr data, uint length)
|
||||
{
|
||||
if (GCHandle.FromIntPtr(ptr).Target is Connection connection)
|
||||
return connection.Receive(data, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Send(IntPtr data, uint length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
Marshal.Copy(data, buffer, 0, (int) length);
|
||||
try
|
||||
{
|
||||
return _socket.Send(buffer);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int SendNative(IntPtr ptr, IntPtr data, uint length)
|
||||
{
|
||||
if (GCHandle.FromIntPtr(ptr).Target is Connection connection)
|
||||
return connection.Send(data, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Timeout(int seconds)
|
||||
{
|
||||
if (seconds < 0)
|
||||
_timer = null;
|
||||
else
|
||||
_timer = new Timer(HandleTimeout, this, seconds * 1000, global::System.Threading.Timeout.Infinite);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int TimeoutNative(IntPtr ptr, int seconds)
|
||||
{
|
||||
if (GCHandle.FromIntPtr(ptr).Target is Connection connection)
|
||||
return connection.Timeout(seconds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
243
VpSharp/src/Internal/Native.cs
Normal file
243
VpSharp/src/Internal/Native.cs
Normal file
@ -0,0 +1,243 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using VpSharp.Internal.NativeAttributes;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal static class Native
|
||||
{
|
||||
private const int NativeSdkVersion = 5;
|
||||
private const string VpSdkLibrary = "vpsdk";
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_init(int version = NativeSdkVersion);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr vp_create(ref NetConfig net_config);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_destroy(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_connect_universe(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string host,
|
||||
int port);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_login(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string username,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string password,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string botname);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_wait(IntPtr instance, int milliseconds);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_enter(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string world_name);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_leave(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_say(
|
||||
IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string message);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_console_message(IntPtr instance,
|
||||
int session,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string name,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string message,
|
||||
int effects,
|
||||
byte red,
|
||||
byte green,
|
||||
byte blue);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_event_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] NativeEvent event_name,
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)] NativeEventHandler @event);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_callback_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] NativeCallback callbackname,
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)] NativeCallbackHandler callback);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_state_change(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_int(IntPtr instance, [MarshalAs(UnmanagedType.I4)] IntegerAttribute attr);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern float vp_float(IntPtr instance, [MarshalAs(UnmanagedType.I4)] FloatAttribute attr);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern double vp_double(IntPtr instance, [MarshalAs(UnmanagedType.I4)] FloatAttribute attr);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToManaged))]
|
||||
public static extern string vp_string(IntPtr instance, [MarshalAs(UnmanagedType.I4)] StringAttribute attr);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr vp_data(IntPtr instance, [MarshalAs(UnmanagedType.I4)] DataAttribute attr, out int length);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_int_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] IntegerAttribute name,
|
||||
int value);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_float_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] FloatAttribute name,
|
||||
float value);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_double_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] FloatAttribute attr,
|
||||
double value);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void vp_string_set(IntPtr instance,
|
||||
StringAttribute name,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string str);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_data_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.I4)] DataAttribute name,
|
||||
int length,
|
||||
byte[] data);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_query_cell(IntPtr instance, int x, int z);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_query_cell_revision(IntPtr instance, int x, int z, int revision);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_add(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_load(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_bump_begin(IntPtr instance, int object_id, int session_to);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_bump_end(IntPtr instance, int object_id, int session_to);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_change(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_click(IntPtr instance,
|
||||
int object_id,
|
||||
int session_to,
|
||||
float hit_x,
|
||||
float hit_y,
|
||||
float hit_z);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_delete(IntPtr instance, int object_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_object_get(IntPtr instance, int object_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_world_list(IntPtr instance, int time);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_user_attributes_by_id(IntPtr instance, int user_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_user_attributes_by_name(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string name);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_friends_get(IntPtr instance);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_friend_add_by_name(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string name);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_friend_delete(IntPtr instance, int friend_user_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_terrain_query(IntPtr instance, int tile_x, int tile_z, int[,] revision);
|
||||
|
||||
// [DllImport(VpSdkLibrary, CallingConvention=CallingConvention.Cdecl)] public static extern int vp_terrain_node_set(IntPtr instance, int tile_x, int tile_z, int node_x, int node_z, struct vp_terrain_cell_t* cells);
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_avatar_click(IntPtr instance, int avatar_session);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_teleport_avatar(IntPtr instance,
|
||||
int target_session,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string world,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
float yaw,
|
||||
float pitch);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_url_send(IntPtr instance,
|
||||
int session_id,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string url,
|
||||
[MarshalAs(UnmanagedType.I4)] UriTarget url_target);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_join(IntPtr instance, int user_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_join_accept(IntPtr instance,
|
||||
int requestId,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string world,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
float yaw,
|
||||
float pitch);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_join_decline(IntPtr instance, int requestId);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_world_permission_user_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string permission,
|
||||
int user_id,
|
||||
int enable);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_world_permission_session_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string permission,
|
||||
int session_id,
|
||||
int enable);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_world_setting_set(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string setting,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string value,
|
||||
int session_to);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_invite(IntPtr instance,
|
||||
int user_id,
|
||||
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string world,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
float yaw,
|
||||
float pitch);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_invite_accept(IntPtr instance, int invitation_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_invite_decline(IntPtr instance, int invitation_id);
|
||||
|
||||
[DllImport(VpSdkLibrary, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int vp_net_notify(IntPtr vpConnection, int type, int status);
|
||||
}
|
7
VpSharp/src/Internal/NativeAttributes/DataAttribute.cs
Normal file
7
VpSharp/src/Internal/NativeAttributes/DataAttribute.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace VpSharp.Internal.NativeAttributes;
|
||||
|
||||
internal enum DataAttribute
|
||||
{
|
||||
ObjectData,
|
||||
TerrainNodeData
|
||||
}
|
43
VpSharp/src/Internal/NativeAttributes/FloatAttribute.cs
Normal file
43
VpSharp/src/Internal/NativeAttributes/FloatAttribute.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace VpSharp.Internal.NativeAttributes;
|
||||
|
||||
internal enum FloatAttribute
|
||||
{
|
||||
AvatarX,
|
||||
AvatarY,
|
||||
AvatarZ,
|
||||
AvatarYaw,
|
||||
AvatarPitch,
|
||||
MyX,
|
||||
MyY,
|
||||
MyZ,
|
||||
MyYaw,
|
||||
MyPitch,
|
||||
ObjectX,
|
||||
ObjectY,
|
||||
ObjectZ,
|
||||
ObjectRotationX,
|
||||
ObjectRotationY,
|
||||
ObjectRotationZ,
|
||||
ObjectYaw = ObjectRotationX,
|
||||
ObjectPitch = ObjectRotationY,
|
||||
ObjectRoll = ObjectRotationZ,
|
||||
ObjectRotationAngle,
|
||||
TeleportX,
|
||||
TeleportY,
|
||||
TeleportZ,
|
||||
TeleportYaw,
|
||||
TeleportPitch,
|
||||
ClickHitX,
|
||||
ClickHitY,
|
||||
ClickHitZ,
|
||||
JoinX,
|
||||
JoinY,
|
||||
JoinZ,
|
||||
JoinYaw,
|
||||
JoinPitch,
|
||||
InviteX,
|
||||
InviteY,
|
||||
InviteZ,
|
||||
InviteYaw,
|
||||
InvitePitch
|
||||
}
|
49
VpSharp/src/Internal/NativeAttributes/IntegerAttribute.cs
Normal file
49
VpSharp/src/Internal/NativeAttributes/IntegerAttribute.cs
Normal file
@ -0,0 +1,49 @@
|
||||
namespace VpSharp.Internal.NativeAttributes;
|
||||
|
||||
internal enum IntegerAttribute
|
||||
{
|
||||
AvatarSession,
|
||||
AvatarType,
|
||||
MyType,
|
||||
ObjectId,
|
||||
ObjectType,
|
||||
ObjectTime,
|
||||
ObjectUserId,
|
||||
WorldState,
|
||||
WorldUsers,
|
||||
ReferenceNumber,
|
||||
Callback,
|
||||
UserId,
|
||||
UserRegistrationTime,
|
||||
UserOnlineTime,
|
||||
UserLastLogin,
|
||||
[Obsolete] FriendId,
|
||||
FriendUserId,
|
||||
FriendOnline,
|
||||
MyUserId,
|
||||
ProxyType,
|
||||
ProxyPort,
|
||||
CellX,
|
||||
CellZ,
|
||||
TerrainTileX,
|
||||
TerrainTileZ,
|
||||
TerrainNodeX,
|
||||
TerrainNodeZ,
|
||||
TerrainNodeRevision,
|
||||
ClickedSession,
|
||||
ChatType,
|
||||
ChatRolorRed,
|
||||
ChatColorGreen,
|
||||
ChatColorBlue,
|
||||
ChatEffects,
|
||||
DisconnectErrorCode,
|
||||
UrlTarget,
|
||||
CurrentEvent,
|
||||
CurrentCallback,
|
||||
CellRevision,
|
||||
CellStatus,
|
||||
JoinId,
|
||||
InviteId,
|
||||
InviteUserId,
|
||||
WorldSize
|
||||
}
|
28
VpSharp/src/Internal/NativeAttributes/StringAttribute.cs
Normal file
28
VpSharp/src/Internal/NativeAttributes/StringAttribute.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace VpSharp.Internal.NativeAttributes;
|
||||
|
||||
internal enum StringAttribute
|
||||
{
|
||||
AvatarName,
|
||||
ChatMessage,
|
||||
ObjectModel,
|
||||
ObjectAction,
|
||||
ObjectDescription,
|
||||
WorldName,
|
||||
UserName,
|
||||
UserEmail,
|
||||
WorldSettingKey,
|
||||
WorldSettingValue,
|
||||
FriendName,
|
||||
ProxyHost,
|
||||
TeleportWorld,
|
||||
Url,
|
||||
JoinWorld,
|
||||
JoinName,
|
||||
StartWorld,
|
||||
InviteName,
|
||||
InviteWorld,
|
||||
ApplicationName,
|
||||
ApplicationVersion,
|
||||
AvatarApplicationName,
|
||||
AvatarApplicationVersion
|
||||
}
|
24
VpSharp/src/Internal/NativeCallback.cs
Normal file
24
VpSharp/src/Internal/NativeCallback.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal enum NativeCallback
|
||||
{
|
||||
ObjectAdd,
|
||||
ObjectChange,
|
||||
ObjectDelete,
|
||||
GetFriends,
|
||||
FriendAdd,
|
||||
FriendDelete,
|
||||
TerrainQuery,
|
||||
TerrainNodeSet,
|
||||
ObjectGet,
|
||||
ObjectLoad,
|
||||
Login,
|
||||
Enter,
|
||||
Join,
|
||||
ConnectUniverse,
|
||||
WorldPermissionUserSet,
|
||||
WorldPermissionSessionSet,
|
||||
WorldSettingSet,
|
||||
Invite,
|
||||
WorldList
|
||||
}
|
6
VpSharp/src/Internal/NativeCallbackHandler.cs
Normal file
6
VpSharp/src/Internal/NativeCallbackHandler.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeCallbackHandler(IntPtr sender, [MarshalAs(UnmanagedType.I4)] ReasonCode reason, int reference);
|
30
VpSharp/src/Internal/NativeEvent.cs
Normal file
30
VpSharp/src/Internal/NativeEvent.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal enum NativeEvent
|
||||
{
|
||||
Chat,
|
||||
AvatarAdd,
|
||||
AvatarChange,
|
||||
AvatarDelete,
|
||||
Object,
|
||||
ObjectChange,
|
||||
ObjectDelete,
|
||||
ObjectClick,
|
||||
WorldList,
|
||||
WorldSetting,
|
||||
WorldSettingsChanged,
|
||||
Friend,
|
||||
WorldDisconnect,
|
||||
UniverseDisconnect,
|
||||
UserAttributes,
|
||||
QueryCellEnd,
|
||||
TerrainNode,
|
||||
AvatarClick,
|
||||
Teleport,
|
||||
Url,
|
||||
ObjectBumpBegin,
|
||||
ObjectBumpEnd,
|
||||
TerrainNodeChanged,
|
||||
Join,
|
||||
Invite
|
||||
}
|
6
VpSharp/src/Internal/NativeEventHandler.cs
Normal file
6
VpSharp/src/Internal/NativeEventHandler.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeEventHandler(IntPtr sender);
|
16
VpSharp/src/Internal/NetConfig.cs
Normal file
16
VpSharp/src/Internal/NetConfig.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
internal struct NetConfig
|
||||
{
|
||||
public SocketCreateFunction Create;
|
||||
public SocketDestroyFunction Destroy;
|
||||
public SocketConnectFunction Connect;
|
||||
public SocketSendFunction Send;
|
||||
public SocketReceiveFunction Receive;
|
||||
public SocketTimeoutFunction Timeout;
|
||||
public SocketWaitFunction Wait;
|
||||
public IntPtr Context;
|
||||
}
|
8
VpSharp/src/Internal/ObjectBuilderMode.cs
Normal file
8
VpSharp/src/Internal/ObjectBuilderMode.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal enum ObjectBuilderMode
|
||||
{
|
||||
Create,
|
||||
Modify,
|
||||
Load
|
||||
}
|
20
VpSharp/src/Internal/ObjectReferenceCounter.cs
Normal file
20
VpSharp/src/Internal/ObjectReferenceCounter.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal static class ObjectReferenceCounter
|
||||
{
|
||||
private static readonly ReaderWriterLockSlim Rwl = new();
|
||||
|
||||
private static int s_reference = int.MinValue;
|
||||
|
||||
internal static int GetNextReference()
|
||||
{
|
||||
int ret;
|
||||
Rwl.EnterWriteLock();
|
||||
if (s_reference < int.MaxValue)
|
||||
ret = s_reference++;
|
||||
else
|
||||
ret = s_reference = int.MinValue;
|
||||
Rwl.ExitWriteLock();
|
||||
return ret;
|
||||
}
|
||||
}
|
8
VpSharp/src/Internal/ObjectType.cs
Normal file
8
VpSharp/src/Internal/ObjectType.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal enum ObjectType
|
||||
{
|
||||
Model,
|
||||
ParticleEmitter = 2,
|
||||
Path
|
||||
}
|
158
VpSharp/src/Internal/ReasonCode.cs
Normal file
158
VpSharp/src/Internal/ReasonCode.cs
Normal file
@ -0,0 +1,158 @@
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal enum ReasonCode
|
||||
{
|
||||
/// <summary>
|
||||
/// The operation was successful.
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The versions of the API and SDK do not match.
|
||||
/// </summary>
|
||||
VersionMismatch,
|
||||
|
||||
/// <summary>
|
||||
/// The instance has not yet been initialized.
|
||||
/// </summary>
|
||||
[Obsolete("This value is no longer returned by the native SDK.")]
|
||||
NotInitialized,
|
||||
|
||||
/// <summary>
|
||||
/// The instance has already been initialized.
|
||||
/// </summary>
|
||||
[Obsolete("This value is no longer returned by the native SDK.")]
|
||||
AlreadyInitialized,
|
||||
|
||||
/// <summary>
|
||||
/// The specified string value is too long.
|
||||
/// </summary>
|
||||
StringTooLong,
|
||||
|
||||
/// <summary>
|
||||
/// The specified username and password constitute an invalid login request.
|
||||
/// </summary>
|
||||
InvalidLogin,
|
||||
|
||||
/// <summary>
|
||||
/// The specified world was not found.
|
||||
/// </summary>
|
||||
WorldNotFound,
|
||||
|
||||
/// <summary>
|
||||
/// There was an error logging into the world.
|
||||
/// </summary>
|
||||
WorldLoginError,
|
||||
|
||||
/// <summary>
|
||||
/// A world request was made while not being connected to a world server.
|
||||
/// </summary>
|
||||
NotInWorld,
|
||||
|
||||
/// <summary>
|
||||
/// An error occured in connection.
|
||||
/// </summary>
|
||||
ConnectionError,
|
||||
|
||||
/// <summary>
|
||||
/// An operation was attempted when no instance was created.
|
||||
/// </summary>
|
||||
[Obsolete("This value is no longer returned by the native SDK.")]
|
||||
NoInstance,
|
||||
|
||||
/// <summary>
|
||||
/// The requested operation is not implemented.
|
||||
/// </summary>
|
||||
NotImplemented,
|
||||
|
||||
/// <summary>
|
||||
/// The requested attribute does not exist.
|
||||
/// </summary>
|
||||
NoSuchAttribute,
|
||||
|
||||
/// <summary>
|
||||
/// The requested operation is not allowed.
|
||||
/// </summary>
|
||||
NotAllowed,
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred in the Universe database.
|
||||
/// </summary>
|
||||
DatabaseError,
|
||||
|
||||
/// <summary>
|
||||
/// The user does not exist.
|
||||
/// </summary>
|
||||
NoSuchUser,
|
||||
|
||||
/// <summary>
|
||||
/// Timeout
|
||||
/// </summary>
|
||||
Timeout,
|
||||
|
||||
/// <summary>
|
||||
/// The instance is not connected to a universe.
|
||||
/// </summary>
|
||||
NotInUniverse,
|
||||
|
||||
/// <summary>
|
||||
/// A function was called with invalid arguments.
|
||||
/// </summary>
|
||||
InvalidArguments,
|
||||
|
||||
/// <summary>
|
||||
/// The queried ID does not belong to an object.
|
||||
/// </summary>
|
||||
ObjectNotFound,
|
||||
|
||||
/// <summary>
|
||||
/// An unknown error occurred.
|
||||
/// </summary>
|
||||
UnknownError,
|
||||
|
||||
/// <summary>
|
||||
/// <c>vp_wait</c> was called recursively.
|
||||
/// </summary>
|
||||
[Obsolete("This value is not returned by the managed SDK.")]
|
||||
RecursiveWait,
|
||||
|
||||
/// <summary>
|
||||
/// The join request was declined.
|
||||
/// </summary>
|
||||
JoinDeclined,
|
||||
|
||||
/// <summary>
|
||||
/// A secure connection is required for the operation.
|
||||
/// </summary>
|
||||
SecureConnectionRequired,
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred when attempting to initiate a secure handshake.
|
||||
/// </summary>
|
||||
HandshakeFailed,
|
||||
|
||||
/// <summary>
|
||||
/// Verification failed.
|
||||
/// </summary>
|
||||
VerificationFailed,
|
||||
|
||||
/// <summary>
|
||||
/// The queried session does not belong to an avatar.
|
||||
/// </summary>
|
||||
NoSuchSession,
|
||||
|
||||
/// <summary>
|
||||
/// The operation is not supported.
|
||||
/// </summary>
|
||||
NotSupported,
|
||||
|
||||
/// <summary>
|
||||
/// The invite request was declined.
|
||||
/// </summary>
|
||||
InviteDeclined,
|
||||
|
||||
/// <summary>
|
||||
/// The created or modified object was placed beyond the bounds of the world.
|
||||
/// </summary>
|
||||
OutOfBounds
|
||||
}
|
9
VpSharp/src/Internal/SocketConnectFunction.cs
Normal file
9
VpSharp/src/Internal/SocketConnectFunction.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int SocketConnectFunction(
|
||||
IntPtr socket,
|
||||
IntPtr host, //[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8StringToNative))] string host,
|
||||
ushort port);
|
6
VpSharp/src/Internal/SocketCreateFunction.cs
Normal file
6
VpSharp/src/Internal/SocketCreateFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate IntPtr SocketCreateFunction(IntPtr connection, IntPtr context);
|
6
VpSharp/src/Internal/SocketDestroyFunction.cs
Normal file
6
VpSharp/src/Internal/SocketDestroyFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void SocketDestroyFunction(IntPtr socket);
|
6
VpSharp/src/Internal/SocketReceiveFunction.cs
Normal file
6
VpSharp/src/Internal/SocketReceiveFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int SocketReceiveFunction(IntPtr socket, IntPtr data, uint length);
|
6
VpSharp/src/Internal/SocketSendFunction.cs
Normal file
6
VpSharp/src/Internal/SocketSendFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int SocketSendFunction(IntPtr socket, IntPtr data, uint length);
|
6
VpSharp/src/Internal/SocketTimeoutFunction.cs
Normal file
6
VpSharp/src/Internal/SocketTimeoutFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int SocketTimeoutFunction(IntPtr socket, int seconds);
|
6
VpSharp/src/Internal/SocketWaitFunction.cs
Normal file
6
VpSharp/src/Internal/SocketWaitFunction.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int SocketWaitFunction(IntPtr context, int duration);
|
34
VpSharp/src/Internal/ThrowHelper.cs
Normal file
34
VpSharp/src/Internal/ThrowHelper.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using VpSharp.Exceptions;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
public static InvalidOperationException CannotUseSelfException() => new(ExceptionMessages.CannotUseSelf);
|
||||
|
||||
[DoesNotReturn]
|
||||
public static void ThrowCannotUseSelfException() => throw CannotUseSelfException();
|
||||
|
||||
public static InvalidOperationException NotInWorldException() => new(ExceptionMessages.NotInWorld);
|
||||
|
||||
[DoesNotReturn]
|
||||
public static void ThrowNotInWorldException() => throw NotInWorldException();
|
||||
|
||||
public static ObjectNotFoundException ObjectNotFoundException() => new(ExceptionMessages.ObjectNotFound);
|
||||
|
||||
[DoesNotReturn]
|
||||
public static void ThrowObjectNotFoundException() => throw ObjectNotFoundException();
|
||||
|
||||
public static ArgumentException StringTooLongException(string paramName) =>
|
||||
new(ExceptionMessages.StringTooLong, paramName);
|
||||
|
||||
[DoesNotReturn]
|
||||
public static void ThrowStringTooLongException(string paramName) => throw StringTooLongException(paramName);
|
||||
|
||||
public static ArgumentOutOfRangeException ZeroThroughOneException(string paramName) =>
|
||||
new(paramName, ExceptionMessages.ZeroThroughOne);
|
||||
|
||||
[DoesNotReturn]
|
||||
public static void ThrowZeroThroughOneException(string paramName) => throw ZeroThroughOneException(paramName);
|
||||
}
|
60
VpSharp/src/Internal/Utf8StringToManaged.cs
Normal file
60
VpSharp/src/Internal/Utf8StringToManaged.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal sealed class Utf8StringToManaged : ICustomMarshaler
|
||||
{
|
||||
private static Utf8StringToManaged? s_instance;
|
||||
|
||||
public static ICustomMarshaler GetInstance(string cookie)
|
||||
{
|
||||
return s_instance ??= new Utf8StringToManaged();
|
||||
}
|
||||
|
||||
public void CleanUpNativeData(IntPtr pNativeData)
|
||||
{
|
||||
// Do nothing. For some reason CleanUpNativeData is called for pointers that are not even created by the marshaler.
|
||||
// This can cause heap corruption because of double free or because a different allocator may have been used to
|
||||
// allocate the memory block.
|
||||
}
|
||||
|
||||
public void CleanUpManagedData(object managedObj)
|
||||
{
|
||||
}
|
||||
|
||||
public int GetNativeDataSize()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IntPtr MarshalManagedToNative(object managedObj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private int GetStringLength(IntPtr ptr)
|
||||
{
|
||||
int offset;
|
||||
for (offset = 0; Marshal.ReadByte(ptr, offset) != 0; offset++)
|
||||
{
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public object MarshalNativeToManaged(IntPtr pNativeData)
|
||||
{
|
||||
if (pNativeData == IntPtr.Zero)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
int length = GetStringLength(pNativeData);
|
||||
var buffer = new Span<byte>(pNativeData.ToPointer(), length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
}
|
||||
}
|
||||
}
|
42
VpSharp/src/Internal/Utf8StringToNative.cs
Normal file
42
VpSharp/src/Internal/Utf8StringToNative.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace VpSharp.Internal;
|
||||
|
||||
internal sealed class Utf8StringToNative : ICustomMarshaler
|
||||
{
|
||||
private static Utf8StringToNative? s_instance;
|
||||
|
||||
public static ICustomMarshaler GetInstance(string cookie)
|
||||
{
|
||||
return s_instance ??= new Utf8StringToNative();
|
||||
}
|
||||
|
||||
public void CleanUpManagedData(object managedObj)
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUpNativeData(IntPtr pNativeData)
|
||||
{
|
||||
Marshal.FreeHGlobal(pNativeData);
|
||||
}
|
||||
|
||||
public int GetNativeDataSize()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IntPtr MarshalManagedToNative(object managedObj)
|
||||
{
|
||||
byte[] utf8Data = Encoding.UTF8.GetBytes((string)managedObj);
|
||||
IntPtr buffer = Marshal.AllocHGlobal(utf8Data.Length + 1);
|
||||
Marshal.Copy(utf8Data, 0, buffer, utf8Data.Length);
|
||||
Marshal.WriteByte(buffer, utf8Data.Length, 0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public object MarshalNativeToManaged(IntPtr pNativeData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
30
VpSharp/src/Internal/ValueConverters/HexToColorConverter.cs
Normal file
30
VpSharp/src/Internal/ValueConverters/HexToColorConverter.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class HexToColorConverter : ValueConverter<Color>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Color result)
|
||||
{
|
||||
Span<char> buffer = stackalloc char[2];
|
||||
|
||||
reader.Read(buffer);
|
||||
int r = int.Parse(buffer, NumberStyles.HexNumber);
|
||||
|
||||
reader.Read(buffer);
|
||||
int g = int.Parse(buffer, NumberStyles.HexNumber);
|
||||
|
||||
reader.Read(buffer);
|
||||
int b = int.Parse(buffer, NumberStyles.HexNumber);
|
||||
|
||||
result = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Color value)
|
||||
{
|
||||
writer.Write($"{value.R:X2}{value.G:X2}{value.B:X2}");
|
||||
}
|
||||
}
|
27
VpSharp/src/Internal/ValueConverters/IntToBoolConverter.cs
Normal file
27
VpSharp/src/Internal/ValueConverters/IntToBoolConverter.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class IntToBoolConverter : ValueConverter<bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out bool result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
int read;
|
||||
while ((read = reader.Read()) != -1)
|
||||
{
|
||||
var current = (char)read;
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
result = builder.AsSpan().ToInt32() != 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, bool value)
|
||||
{
|
||||
writer.Write(value ? 1 : 0);
|
||||
}
|
||||
}
|
18
VpSharp/src/Internal/ValueConverters/IntToEnumConverter.cs
Normal file
18
VpSharp/src/Internal/ValueConverters/IntToEnumConverter.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class IntToEnumConverter<T> : ValueConverter<T>
|
||||
where T : struct, Enum
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out T result)
|
||||
{
|
||||
int value = int.Parse(reader.ReadToEnd());
|
||||
result = (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, T value)
|
||||
{
|
||||
writer.Write(Convert.ToInt32(value));
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class MillisecondToTimeSpanConverter : ValueConverter<TimeSpan>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out TimeSpan result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
int read;
|
||||
while ((read = reader.Read()) != -1)
|
||||
{
|
||||
var current = (char)read;
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
result = TimeSpan.FromMilliseconds(builder.AsSpan().ToDouble());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, TimeSpan value)
|
||||
{
|
||||
writer.Write(value.TotalMilliseconds);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using System.Reflection;
|
||||
using VpSharp.Internal.Attributes;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class StringToEnumConverter<T> : ValueConverter<T>
|
||||
where T : struct, Enum
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out T result)
|
||||
{
|
||||
string value = reader.ReadToEnd();
|
||||
|
||||
FieldInfo? field = typeof(T).GetFields().FirstOrDefault(f => string.Equals(f.GetCustomAttribute<SerializationKeyAttribute>()?.Key, value));
|
||||
if (field is not null)
|
||||
{
|
||||
result = (T)field.GetValue(Enum.GetValues<T>()[0])!;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Enum.Parse<T>(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, T value)
|
||||
{
|
||||
writer.Write(value.ToString().ToLowerInvariant());
|
||||
}
|
||||
}
|
18
VpSharp/src/Internal/ValueConverters/UriConverter.cs
Normal file
18
VpSharp/src/Internal/ValueConverters/UriConverter.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class UriConverter : ValueConverter<Uri>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Uri result)
|
||||
{
|
||||
string url = reader.ReadToEnd();
|
||||
result = string.IsNullOrWhiteSpace(url) ? null : new Uri(url);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Uri value)
|
||||
{
|
||||
if (value is not null)
|
||||
writer.Write(value.ToString());
|
||||
}
|
||||
}
|
31
VpSharp/src/Internal/ValueConverters/ValueConverter.cs
Normal file
31
VpSharp/src/Internal/ValueConverters/ValueConverter.cs
Normal file
@ -0,0 +1,31 @@
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
[Obsolete]
|
||||
internal abstract class ValueConverter
|
||||
{
|
||||
public abstract void Deserialize(TextReader reader, out object result);
|
||||
|
||||
public abstract void Serialize(TextWriter writer, object value);
|
||||
}
|
||||
|
||||
#pragma warning disable 612
|
||||
internal abstract class ValueConverter<T> : ValueConverter
|
||||
#pragma warning restore 612
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out object result)
|
||||
{
|
||||
Deserialize(reader, out T actual);
|
||||
result = actual!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, object value)
|
||||
{
|
||||
Serialize(writer, (T) value);
|
||||
}
|
||||
|
||||
public abstract void Deserialize(TextReader reader, out T result);
|
||||
|
||||
public abstract void Serialize(TextWriter writer, T value);
|
||||
}
|
36
VpSharp/src/Internal/ValueConverters/Vector2Converter.cs
Normal file
36
VpSharp/src/Internal/ValueConverters/Vector2Converter.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Numerics;
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector2Converter : ValueConverter<Vector2>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Vector2 result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 2 && readChar != -1)
|
||||
continue;
|
||||
|
||||
result = builder.AsSpan().ToVector2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Vector2 value)
|
||||
{
|
||||
writer.Write($"{value.X} {value.Y}");
|
||||
}
|
||||
}
|
36
VpSharp/src/Internal/ValueConverters/Vector3Converter.cs
Normal file
36
VpSharp/src/Internal/ValueConverters/Vector3Converter.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Numerics;
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector3Converter : ValueConverter<Vector3>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Vector3 result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 3 && readChar != -1)
|
||||
continue;
|
||||
|
||||
result = builder.AsSpan().ToVector3();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Vector3 value)
|
||||
{
|
||||
writer.Write($"{value.X} {value.Y} {value.Z}");
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector3ToColorConverter : ValueConverter<ColorF>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out ColorF result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 3 && readChar != -1)
|
||||
continue;
|
||||
|
||||
(float x, float y, float z) = builder.AsSpan().ToVector3();
|
||||
result = ColorF.FromArgb(x, y, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, ColorF value)
|
||||
{
|
||||
writer.Write($"{value.R} {value.G} {value.B} {value.A}");
|
||||
}
|
||||
}
|
35
VpSharp/src/Internal/ValueConverters/Vector3dConverter.cs
Normal file
35
VpSharp/src/Internal/ValueConverters/Vector3dConverter.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector3dConverter : ValueConverter<Vector3d>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Vector3d result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 3 && readChar != -1)
|
||||
continue;
|
||||
|
||||
result = builder.AsSpan().ToVector3d();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Vector3d value)
|
||||
{
|
||||
writer.Write($"{value.X} {value.Y} {value.Z}");
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector4ToColorConverter : ValueConverter<ColorF>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out ColorF result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 4 && readChar != -1)
|
||||
continue;
|
||||
|
||||
(float x, float y, float z, float w) = builder.AsSpan().ToVector4();
|
||||
result = ColorF.FromArgb(w, x, y, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, ColorF value)
|
||||
{
|
||||
writer.Write($"{value.R} {value.G} {value.B} {value.A}");
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using System.Numerics;
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class Vector4ToVector3Converter : ValueConverter<Vector3>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out Vector3 result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
|
||||
if (spaceCount < 3 && readChar != -1)
|
||||
continue;
|
||||
|
||||
result = builder.AsSpan().ToVector3();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, Vector3 value)
|
||||
{
|
||||
writer.Write(value.X);
|
||||
writer.Write(' ');
|
||||
writer.Write(value.Y);
|
||||
writer.Write(' ');
|
||||
writer.Write(value.Z);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using Cysharp.Text;
|
||||
using VpSharp.Extensions;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal sealed class VectorToNthComponentConverter : ValueConverter<float>
|
||||
{
|
||||
private readonly int _componentNumber;
|
||||
|
||||
/// <inheritdoc />
|
||||
public VectorToNthComponentConverter(int componentNumber)
|
||||
{
|
||||
_componentNumber = componentNumber;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Deserialize(TextReader reader, out float result)
|
||||
{
|
||||
using var builder = new Utf8ValueStringBuilder(false);
|
||||
var spaceCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readChar = reader.Read();
|
||||
|
||||
if (readChar == -1)
|
||||
break;
|
||||
|
||||
var currentChar = (char) readChar;
|
||||
if (currentChar == ' ')
|
||||
spaceCount++;
|
||||
else if (spaceCount == _componentNumber - 1)
|
||||
builder.Append(currentChar);
|
||||
}
|
||||
|
||||
result = builder.AsSpan().ToSingle();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Serialize(TextWriter writer, float value)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
129
VpSharp/src/Internal/ValueConverters/WorldSettingsConverter.cs
Normal file
129
VpSharp/src/Internal/ValueConverters/WorldSettingsConverter.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using VpSharp.Internal.Attributes;
|
||||
|
||||
namespace VpSharp.Internal.ValueConverters;
|
||||
|
||||
internal static class WorldSettingsConverter
|
||||
{
|
||||
public static IReadOnlyDictionary<string, string?> ToDictionary(WorldSettingsBuilder settings)
|
||||
{
|
||||
var dictionary = new Dictionary<string, string?>();
|
||||
PropertyInfo[] properties = settings.GetType().GetProperties();
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
var attribute = property.GetCustomAttribute<SerializationKeyAttribute>();
|
||||
if (attribute is null)
|
||||
continue;
|
||||
|
||||
object? propertyValue = property.GetValue(settings);
|
||||
Type propertyType = property.PropertyType;
|
||||
|
||||
if (propertyValue is null)
|
||||
continue;
|
||||
|
||||
var result = propertyValue.ToString();
|
||||
|
||||
var converterAttribute = property.GetCustomAttribute<ValueConverterAttribute>();
|
||||
if (converterAttribute is not null)
|
||||
{
|
||||
#pragma warning disable 612
|
||||
Type converterType = converterAttribute.ConverterType;
|
||||
|
||||
ValueConverter? converter;
|
||||
if (converterAttribute.UseArgs)
|
||||
converter = Activator.CreateInstance(converterType, converterAttribute.Args) as ValueConverter;
|
||||
else
|
||||
converter = Activator.CreateInstance(converterType) as ValueConverter;
|
||||
|
||||
if (converter is not null)
|
||||
{
|
||||
using var writer = new StringWriter();
|
||||
converter.Serialize(writer, propertyValue);
|
||||
result = writer.ToString();
|
||||
}
|
||||
#pragma warning restore 612
|
||||
}
|
||||
else
|
||||
{
|
||||
if (propertyType == typeof(bool) || propertyType == typeof(bool?))
|
||||
result = (bool)propertyValue ? "1" : "0";
|
||||
}
|
||||
|
||||
if (result is not null)
|
||||
dictionary.Add(attribute.Key, result);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public static WorldSettings FromDictionary(IReadOnlyDictionary<string, string> dictionary)
|
||||
{
|
||||
var settings = new WorldSettings();
|
||||
PropertyInfo[] properties = typeof(WorldSettings).GetProperties();
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
var defaultValueAttribute = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||
if (defaultValueAttribute is null)
|
||||
continue;
|
||||
|
||||
property.SetValue(settings, defaultValueAttribute.Value);
|
||||
}
|
||||
|
||||
foreach ((string key, string value) in dictionary)
|
||||
{
|
||||
PropertyInfo? property = properties.FirstOrDefault(p =>
|
||||
string.Equals(p.GetCustomAttribute<SerializationKeyAttribute>()?.Key, key,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (property is null)
|
||||
continue;
|
||||
|
||||
using var reader = new StringReader(value);
|
||||
object propertyValue = value;
|
||||
Type? converterType = null;
|
||||
|
||||
var converterAttribute = property.GetCustomAttribute<ValueConverterAttribute>();
|
||||
if (converterAttribute is not null)
|
||||
{
|
||||
converterType = converterAttribute.ConverterType;
|
||||
}
|
||||
else
|
||||
{
|
||||
Type propertyType = property.PropertyType;
|
||||
|
||||
if (propertyType == typeof(bool))
|
||||
propertyValue = value == "1" || (bool.TryParse(value, out bool result) && result);
|
||||
else if (propertyType == typeof(int))
|
||||
propertyValue = int.TryParse(value, out int result) ? result : 0;
|
||||
else if (propertyType == typeof(float))
|
||||
propertyValue = float.TryParse(value, out float result) ? result : 0.0f;
|
||||
else if (propertyType == typeof(double))
|
||||
propertyValue = double.TryParse(value, out double result) ? result : 0.0;
|
||||
else if (propertyType.IsEnum && int.TryParse(value, out int result))
|
||||
propertyValue = Convert.ChangeType(result, propertyType);
|
||||
}
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalse
|
||||
#pragma warning disable 612
|
||||
if (converterType is not null && converterAttribute is not null)
|
||||
{
|
||||
ValueConverter? converter;
|
||||
if (converterAttribute.UseArgs)
|
||||
converter = Activator.CreateInstance(converterType, converterAttribute.Args) as ValueConverter;
|
||||
else
|
||||
converter = Activator.CreateInstance(converterType) as ValueConverter;
|
||||
|
||||
converter?.Deserialize(reader, out propertyValue);
|
||||
}
|
||||
#pragma warning restore 612
|
||||
// ReSharper restore ConditionIsAlwaysTrueOrFalse
|
||||
|
||||
property.SetValue(settings, propertyValue);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
93
VpSharp/src/InviteRequest.cs
Normal file
93
VpSharp/src/InviteRequest.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using VpSharp.Entities;
|
||||
using VpSharp.Internal;
|
||||
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an invite request, which the client may either accept or decline.
|
||||
/// </summary>
|
||||
public sealed class InviteRequest : IEquatable<InviteRequest>
|
||||
{
|
||||
private readonly VirtualParadiseClient _client;
|
||||
private readonly int _requestId;
|
||||
|
||||
internal InviteRequest(
|
||||
VirtualParadiseClient client,
|
||||
int requestId,
|
||||
string name,
|
||||
VirtualParadiseUser user,
|
||||
Location location)
|
||||
{
|
||||
Name = name;
|
||||
User = user;
|
||||
Location = location;
|
||||
|
||||
_client = client;
|
||||
_requestId = requestId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target location of this invite.
|
||||
/// </summary>
|
||||
public Location Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the avatar which sent the request.
|
||||
/// </summary>
|
||||
/// <value>The name of the avatar which sent the request.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user which sent the request.
|
||||
/// </summary>
|
||||
/// <value>The user which sent the request.</value>
|
||||
public VirtualParadiseUser User { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(InviteRequest? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return _requestId == other._requestId && _client.Equals(other._client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts this invite request.
|
||||
/// </summary>
|
||||
/// <param name="suppressTeleport">
|
||||
/// If <see langword="true" />, the client's avatar will not teleport to the requested location automatically.
|
||||
/// </param>
|
||||
public Task AcceptAsync(bool suppressTeleport = false)
|
||||
{
|
||||
lock (_client.Lock) Native.vp_invite_accept(_client.NativeInstanceHandle, _requestId);
|
||||
|
||||
if (suppressTeleport)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return _client.CurrentAvatar.TeleportAsync(Location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declines this invite request.
|
||||
/// </summary>
|
||||
public Task DeclineAsync()
|
||||
{
|
||||
lock (_client.Lock) Native.vp_invite_decline(_client.NativeInstanceHandle, _requestId);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
return obj is InviteRequest other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => HashCode.Combine(_client, _requestId);
|
||||
|
||||
public static bool operator ==(InviteRequest left, InviteRequest right) => Equals(left, right);
|
||||
|
||||
public static bool operator !=(InviteRequest left, InviteRequest right) => !Equals(left, right);
|
||||
}
|
22
VpSharp/src/InviteResponse.cs
Normal file
22
VpSharp/src/InviteResponse.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of invite responses.
|
||||
/// </summary>
|
||||
public enum InviteResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The invite request was accepted by the user.
|
||||
/// </summary>
|
||||
Accepted,
|
||||
|
||||
/// <summary>
|
||||
/// The invite request was declined by the user.
|
||||
/// </summary>
|
||||
Declined,
|
||||
|
||||
/// <summary>
|
||||
/// The invite request timed out.
|
||||
/// </summary>
|
||||
TimeOut
|
||||
}
|
89
VpSharp/src/JoinRequest.cs
Normal file
89
VpSharp/src/JoinRequest.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using VpSharp.Entities;
|
||||
using VpSharp.Extensions;
|
||||
using VpSharp.Internal;
|
||||
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a join request, which the client may either accept or decline.
|
||||
/// </summary>
|
||||
public sealed class JoinRequest : IEquatable<JoinRequest>
|
||||
{
|
||||
private readonly VirtualParadiseClient _client;
|
||||
private readonly int _requestId;
|
||||
|
||||
internal JoinRequest(VirtualParadiseClient client, int requestId, string name, VirtualParadiseUser user)
|
||||
{
|
||||
Name = name;
|
||||
User = user;
|
||||
_client = client;
|
||||
_requestId = requestId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the avatar which sent the request.
|
||||
/// </summary>
|
||||
/// <value>The name of the avatar which sent the request.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user which sent the request.
|
||||
/// </summary>
|
||||
/// <value>The user which sent the request.</value>
|
||||
public VirtualParadiseUser User { get; }
|
||||
|
||||
public static bool operator ==(JoinRequest left, JoinRequest right) => Equals(left, right);
|
||||
|
||||
public static bool operator !=(JoinRequest left, JoinRequest right) => !Equals(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Accepts this join request.
|
||||
/// </summary>
|
||||
/// <param name="location">
|
||||
/// Optional. The target location of the join. Defaults to the client's current avatar location.
|
||||
/// </param>
|
||||
public Task AcceptAsync(Location? location = null)
|
||||
{
|
||||
if (_client.CurrentAvatar is null)
|
||||
ThrowHelper.ThrowNotInWorldException();
|
||||
|
||||
location ??= _client.CurrentAvatar!.Location;
|
||||
string worldName = location.Value.World.Name;
|
||||
(double x, double y, double z) = location.Value.Position;
|
||||
(double pitch, double yaw, double _) = location.Value.Rotation.ToEulerAngles();
|
||||
|
||||
lock (_client.Lock)
|
||||
Native.vp_join_accept(_client.NativeInstanceHandle, _requestId, worldName, x, y, z, (float) yaw, (float) pitch);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declines this join request.
|
||||
/// </summary>
|
||||
public Task DeclineAsync()
|
||||
{
|
||||
lock (_client.Lock) Native.vp_join_decline(_client.NativeInstanceHandle, _requestId);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(JoinRequest? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return _requestId == other._requestId && _client.Equals(other._client);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
return obj is JoinRequest other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => HashCode.Combine(_client, _requestId);
|
||||
}
|
22
VpSharp/src/JoinResponse.cs
Normal file
22
VpSharp/src/JoinResponse.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of join responses.
|
||||
/// </summary>
|
||||
public enum JoinResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The join request was accepted by the user.
|
||||
/// </summary>
|
||||
Accepted,
|
||||
|
||||
/// <summary>
|
||||
/// The join request was declined by the user.
|
||||
/// </summary>
|
||||
Declined,
|
||||
|
||||
/// <summary>
|
||||
/// The join request timed out.
|
||||
/// </summary>
|
||||
TimeOut
|
||||
}
|
30
VpSharp/src/JoinResult.cs
Normal file
30
VpSharp/src/JoinResult.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a join result.
|
||||
/// </summary>
|
||||
public readonly struct JoinResult
|
||||
{
|
||||
internal JoinResult(JoinResponse response, Location? location)
|
||||
{
|
||||
Response = response;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the join location.
|
||||
/// </summary>
|
||||
/// <value>The join location. This value will be <see langword="null" /> if the request was declined, or timed out.</value>
|
||||
public Location? Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the join response.
|
||||
/// </summary>
|
||||
/// <value>The join response.</value>
|
||||
public JoinResponse Response { get; }
|
||||
|
||||
public static implicit operator bool(JoinResult result)
|
||||
{
|
||||
return result.Response == JoinResponse.Accepted;
|
||||
}
|
||||
}
|
118
VpSharp/src/Location.cs
Normal file
118
VpSharp/src/Location.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using System.Numerics;
|
||||
using VpSharp.Entities;
|
||||
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a location within the Virtual Paradise universe.
|
||||
/// </summary>
|
||||
public readonly struct Location : IEquatable<Location>
|
||||
{
|
||||
/// <summary>
|
||||
/// A location that represents nowhere in the universe.
|
||||
/// </summary>
|
||||
public static readonly Location Nowhere = new(VirtualParadiseWorld.Nowhere);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Location" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="world">The world.</param>
|
||||
/// <param name="position">The position.</param>
|
||||
/// <param name="rotation">The rotation.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="world" /> is <see langword="null" />.</exception>
|
||||
public Location(VirtualParadiseWorld world, Vector3d position = default, Quaternion rotation = default)
|
||||
{
|
||||
World = world ?? throw new ArgumentNullException(nameof(world));
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cell corresponding to this location.
|
||||
/// </summary>
|
||||
public Cell Cell
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = (int) Math.Floor(Position.X);
|
||||
var z = (int) Math.Floor(Position.Z);
|
||||
return new Cell(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position represented by this location.
|
||||
/// </summary>
|
||||
/// <value>The position.</value>
|
||||
public Vector3d Position { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation represented by this location.
|
||||
/// </summary>
|
||||
/// <value>The rotation.</value>
|
||||
public Quaternion Rotation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world represented by this location.
|
||||
/// </summary>
|
||||
/// <value>The world.</value>
|
||||
public VirtualParadiseWorld World { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="Location" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator ==(Location left, Location right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="Location" /> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator !=(Location left, Location right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two <see cref="Location" /> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if this instance is equal to <paramref name="other" />; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public bool Equals(Location other)
|
||||
{
|
||||
return Position.Equals(other.Position) && Rotation.Equals(other.Rotation) && World.Equals(other.World);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Location other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Position, Rotation, World);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Location [World={World}, Position={Position}, Rotation={Rotation}]";
|
||||
}
|
||||
}
|
17
VpSharp/src/MessageType.cs
Normal file
17
VpSharp/src/MessageType.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace VpSharp;
|
||||
|
||||
/// <summary>
|
||||
/// An enumeration of message types.
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
/// <summary>
|
||||
/// A chat message sent by an avatar.
|
||||
/// </summary>
|
||||
ChatMessage,
|
||||
|
||||
/// <summary>
|
||||
/// A console message sent by a bot.
|
||||
/// </summary>
|
||||
ConsoleMessage
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user