1
0
mirror of https://github.com/oliverbooth/VpSharp synced 2024-11-09 23:35:41 +00:00

VpSharp.Says("Hello World");

This commit is contained in:
Oliver Booth 2022-11-27 15:28:07 +00:00
parent eda59e9d7e
commit 56aae0116e
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
124 changed files with 9264 additions and 0 deletions

View 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>

View 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);
}
}

View File

@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;

52
VpSharp/VpSharp.csproj Normal file
View 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>

View 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
View 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&apos;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);
}
}
}
}

View 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>

View 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
View File

@ -0,0 +1 @@
[assembly: CLSCompliant(true)]

17
VpSharp/src/BumpPhase.cs Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,11 @@
namespace VpSharp;
/// <summary>
/// An enumeration of color spaces.
/// </summary>
public enum ColorSpace
{
Linear,
// ReSharper disable once InconsistentNaming
sRGB
}

View 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
}

View 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}]";
}
}

View 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; }
}

View 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);
}
}

View 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);
}
}
}

View 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)
{
}
}

View 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; }
}

View 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;
}
}
}
}

View File

@ -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;
}
}

View 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);
}
}

View 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);
}
}

View 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}]";
}
}

View 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}]";
}
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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)
{
}
}

View 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)
{
}
}

View 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)
{
}
}

View 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)
{
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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
View 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
}

View 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; }
}

View 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; }
}

View 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;
}
}

View 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);
}

View File

@ -0,0 +1,7 @@
namespace VpSharp.Internal.NativeAttributes;
internal enum DataAttribute
{
ObjectData,
TerrainNodeData
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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);

View 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
}

View File

@ -0,0 +1,6 @@
using System.Runtime.InteropServices;
namespace VpSharp.Internal;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeEventHandler(IntPtr sender);

View 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;
}

View File

@ -0,0 +1,8 @@
namespace VpSharp.Internal;
internal enum ObjectBuilderMode
{
Create,
Modify,
Load
}

View 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;
}
}

View File

@ -0,0 +1,8 @@
namespace VpSharp.Internal;
internal enum ObjectType
{
Model,
ParticleEmitter = 2,
Path
}

View 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
}

View 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);

View File

@ -0,0 +1,6 @@
using System.Runtime.InteropServices;
namespace VpSharp.Internal;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate IntPtr SocketCreateFunction(IntPtr connection, IntPtr context);

View File

@ -0,0 +1,6 @@
using System.Runtime.InteropServices;
namespace VpSharp.Internal;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void SocketDestroyFunction(IntPtr socket);

View 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);

View 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);

View File

@ -0,0 +1,6 @@
using System.Runtime.InteropServices;
namespace VpSharp.Internal;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int SocketTimeoutFunction(IntPtr socket, int seconds);

View File

@ -0,0 +1,6 @@
using System.Runtime.InteropServices;
namespace VpSharp.Internal;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int SocketWaitFunction(IntPtr context, int duration);

View 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);
}

View 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);
}
}
}

View 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();
}
}

View 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}");
}
}

View 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);
}
}

View 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));
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View 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());
}
}

View 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);
}

View 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}");
}
}

View 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}");
}
}

View File

@ -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}");
}
}

View 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}");
}
}

View File

@ -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}");
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View 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;
}
}

View 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);
}

View 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
}

View 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);
}

View 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
View 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
View 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}]";
}
}

View 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