Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
jhett12321 committed Mar 3, 2023
2 parents b914efd + d693222 commit 1f2ccd6
Show file tree
Hide file tree
Showing 208 changed files with 1,908 additions and 445 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## 8193.34.25
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.24...v8193.34.25

### Added
- Exposed `HomeStorage` class for accessing paths in anvil home.
- Added support for creating `Cassowary` solvers through `new Cassowary()`
- NwGameObject: Added `ActionJumpToLocation` method.
- Events: Added `OnPlayerQuickChat` event.
- NwPlayer: Added object name override support (SetObjectNameOverride)
- NwPlayer: Added player-specific looping vfx support (AddLoopingVisualEffect)
- NwCreature: Added `LevelUp` method that bypasses validation.

### Package Updates
- NLog: 5.1.1 -> 5.1.2
- Microsoft.CodeAnalysis.CSharp: 4.4.0 -> 4.5.0

### Changed
- Optional anvil services will now log a message when they are used.

### Fixed
- !! Fixed a memory leak when not using `Dispose()` on engine structures. (Effect, Location, ItemProperty, Json, SQLQuery, Talent)

## 8193.34.24
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.23...v8193.34.24

Expand Down
7 changes: 6 additions & 1 deletion NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
Expand All @@ -68,6 +68,11 @@
</ProjectReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="NWN.Core" Version="8193.34.12" PrivateAssets="compile" />
<PackageReference Include="NWN.Native" Version="8193.34.5" PrivateAssets="compile" />
</ItemGroup>

<ItemGroup>
<None Remove="src\lib\nunit\CONTRIBUTING.md" />
<None Remove="src\lib\nunit\CODE_OF_CONDUCT.md" />
Expand Down
10 changes: 5 additions & 5 deletions NWN.Anvil.TestRunner/src/main/TestRunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ internal sealed class TestRunnerService
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();

private readonly NwServer nwServer;
private readonly MainThreadSynchronizationContext mainThreadSynchronizationContext;

private readonly Queue<Assembly> testAssemblyQueue = new Queue<Assembly>();
private readonly string outputDir;

private Thread testWorkerThread;

public TestRunnerService(PluginStorageService pluginStorageService, NwServer nwServer)
public TestRunnerService(MainThreadSynchronizationContext mainThreadSynchronizationContext, PluginStorageService pluginStorageService)
{
this.nwServer = nwServer;
this.mainThreadSynchronizationContext = mainThreadSynchronizationContext;

outputDir = pluginStorageService.GetPluginStoragePath(typeof(TestRunnerService).Assembly);
NwModule.Instance.OnModuleLoad += OnModuleLoad;
Expand All @@ -49,7 +49,7 @@ private void PopulateTestQueue()

private void OnModuleLoad(ModuleEvents.OnModuleLoad eventData)
{
TestCommand.DefaultSynchronizationContext = NwTask.MainThreadSynchronizationContext;
TestCommand.DefaultSynchronizationContext = mainThreadSynchronizationContext;
testWorkerThread = new Thread(RunTests);
testWorkerThread.Start();
}
Expand All @@ -72,7 +72,7 @@ private async void Shutdown()
{
testWorkerThread = null;
await NwTask.SwitchToMainThread();
nwServer.ShutdownServer();
NwServer.Instance.ShutdownServer();
}

private string[] GetRunnerArguments(Assembly assembly)
Expand Down
3 changes: 3 additions & 0 deletions NWN.Anvil.Tests/NWN.Anvil.Tests.csproj.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Casync/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cenginestructure/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cenginestructures/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Cbindings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Clayout/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Cwidgets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cobject/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cobjects/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Ctlk/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Ctwodimarray/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cutils/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cvariable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cvariables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cresources/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cscheduler/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cservices/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
50 changes: 50 additions & 0 deletions NWN.Anvil.Tests/src/main/API/EngineStructures/CassowaryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Anvil.API;
using NUnit.Framework;

namespace Anvil.Tests.API
{
[TestFixture(Category = "API.EngineStructure")]
public sealed class CassowaryTests
{
[Test(Description = "Creating a new cassowary and disposing the cassowary explicitly frees the associated memory.")]
public void CreateAndDisposeCassowaryValidPropertyUpdated()
{
Cassowary cassowary = new Cassowary();
Assert.That(cassowary.IsValid, Is.True, "Cassowary was not valid after creation.");
cassowary.Dispose();
Assert.That(cassowary.IsValid, Is.False, "Cassowary was still valid after disposing.");
}

[Test(Description = "A cassowary correctly finds a solution from a set of constraints.")]
public void CreateCassowaryConstraintReturnsValidSolution()
{
Cassowary cassowary = new Cassowary();

cassowary.AddConstraint("middle == (left + right) / 2");
cassowary.AddConstraint("right == left + 10");
cassowary.AddConstraint("right <= 100");
cassowary.AddConstraint("left >= 0");

Assert.That(cassowary.GetValue("left"), Is.EqualTo(90f));
Assert.That(cassowary.GetValue("middle"), Is.EqualTo(95f));
Assert.That(cassowary.GetValue("right"), Is.EqualTo(100f));
}

[Test(Description = "A cassowary correctly finds a solution from a set of constraints and a suggested value.")]
public void CreateCassowaryConstraintWithSuggestedValueReturnsValidSolution()
{
Cassowary cassowary = new Cassowary();

cassowary.AddConstraint("middle == (left + right) / 2");
cassowary.AddConstraint("right == left + 10");
cassowary.AddConstraint("right <= 100");
cassowary.AddConstraint("left >= 0");

cassowary.SuggestValue("middle", 45f);

Assert.That(cassowary.GetValue("left"), Is.EqualTo(40f));
Assert.That(cassowary.GetValue("middle"), Is.EqualTo(45f));
Assert.That(cassowary.GetValue("right"), Is.EqualTo(50f));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Anvil.Tests.API
public sealed class EffectTests
{
[Test(Description = "Creating an effect and disposing the effect explicitly frees the associated memory.")]
public void CreateAndDisposeEffectValidPropertyUpdated()
public void CreateAndDisposeEffectFreesNativeStructure()
{
Effect effect = Effect.CutsceneParalyze();
Assert.That(effect.IsValid, Is.True, "Effect was not valid after creation.");
Expand All @@ -17,7 +17,7 @@ public void CreateAndDisposeEffectValidPropertyUpdated()
}

[Test(Description = "A soft effect reference created from a native object does not cause the original effect to be deleted.")]
public void CreateSoftEffectReferencAndDisposeDoesNotFreeMemory()
public void CreateSoftEffectReferenceAndDisposeDoesNotFreeMemory()
{
Effect effect = Effect.Blindness();
Assert.That(effect.IsValid, Is.True, "Effect was not valid after creation.");
Expand Down
34 changes: 34 additions & 0 deletions NWN.Anvil.Tests/src/main/API/EngineStructures/ItemPropertyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Anvil.API;
using NUnit.Framework;
using NWN.Native.API;
using ItemProperty = Anvil.API.ItemProperty;

namespace Anvil.Tests.API
{
[TestFixture(Category = "API.EngineStructure")]
public sealed class ItemPropertyTests
{
[Test(Description = "Creating an item property and disposing the item property explicitly frees the associated memory.")]
public void CreateAndDisposeItemPropertyFreesNativeStructure()
{
ItemProperty itemProperty = ItemProperty.Haste();
Assert.That(itemProperty.IsValid, Is.True, "Item property was not valid after creation.");
itemProperty.Dispose();
Assert.That(itemProperty.IsValid, Is.False, "Item property was still valid after disposing.");
}

[Test(Description = "A soft item property reference created from a native object does not cause the original item property to be deleted.")]
public void CreateSoftItemPropertyReferenceAndDisposeDoesNotFreeMemory()
{
ItemProperty itemProperty = ItemProperty.Haste();
Assert.That(itemProperty.IsValid, Is.True, "Item property was not valid after creation.");

CGameEffect gameEffect = itemProperty;
Assert.That(gameEffect, Is.Not.Null, "Native Item property was not valid after implicit cast.");

ItemProperty softReference = gameEffect.ToItemProperty(false)!;
softReference.Dispose();
Assert.That(softReference.IsValid, Is.True, "The soft reference disposed the memory of the original item property.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void ApplyEnvironmentPresetIsApplied(int presetRowIndex)
{
EnvironmentPreset preset = NwGameTables.EnvironmentPresetTable.GetRow(presetRowIndex);

NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;
area.ApplyEnvironmentPreset(preset);

Assert.That(area.DayNightMode, Is.EqualTo(preset.DayNightMode));
Expand Down Expand Up @@ -66,7 +66,7 @@ public void ApplyEnvironmentPresetIsApplied(int presetRowIndex)
[TestCase(AreaFlags.UnderGround)]
public void ChangeAreaFlagsUpdatesAreaFlags(AreaFlags flag)
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;
area.AreaFlags |= flag;
Assert.That(area.AreaFlags.HasFlag(flag), Is.EqualTo(true));
area.AreaFlags &= ~flag;
Expand All @@ -76,7 +76,7 @@ public void ChangeAreaFlagsUpdatesAreaFlags(AreaFlags flag)
[Test(Description = "Changing the IsInterior value correctly updates the area flag.")]
public void ChangeAreaInteriorUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;
area.IsInterior = true;
area.IsAboveGround = true; // NWScript.GetIsAreaInterior() always returns true if this flag is set to false.

Expand All @@ -95,7 +95,7 @@ public void ChangeAreaInteriorUpdatesAreaFlags()
[Test(Description = "Changing the IsExterior value correctly updates the area flag.")]
public void ChangeAreaExteriorUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;
area.IsExterior = true;
area.IsAboveGround = true; // NWScript.GetIsAreaInterior() always returns true if this flag is set to false.

Expand All @@ -114,7 +114,7 @@ public void ChangeAreaExteriorUpdatesAreaFlags()
[Test(Description = "Changing the IsNatural value correctly updates the area flag.")]
public void ChangeAreaNaturalUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;

area.IsNatural = true;
Assert.That(area.IsNatural, Is.EqualTo(true));
Expand All @@ -132,7 +132,7 @@ public void ChangeAreaNaturalUpdatesAreaFlags()
[Test(Description = "Changing the IsUrban value correctly updates the area flag.")]
public void ChangeAreaUrbanUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;

area.IsUrban = true;
Assert.That(area.IsUrban, Is.EqualTo(true));
Expand All @@ -150,7 +150,7 @@ public void ChangeAreaUrbanUpdatesAreaFlags()
[Test(Description = "Changing the IsAboveGround value correctly updates the area flag.")]
public void ChangeAreaAboveGroundUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;

area.IsAboveGround = true;
Assert.That(area.IsAboveGround, Is.EqualTo(true));
Expand All @@ -168,7 +168,7 @@ public void ChangeAreaAboveGroundUpdatesAreaFlags()
[Test(Description = "Changing the IsUnderGround value correctly updates the area flag.")]
public void ChangeAreaUnderGroundUpdatesAreaFlags()
{
NwArea area = NwModule.Instance.StartingLocation.Area!;
NwArea area = NwModule.Instance.StartingLocation.Area;

area.IsUnderGround = true;
Assert.That(area.IsUnderGround, Is.EqualTo(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,57 @@ public void SetFeatRemainingUsesCorrectlyUpdatesState(byte uses)
Assert.That(creature.HasFeatPrepared(feat!), Is.EqualTo(uses > 0), "Creature incorrectly assumes the feat is/is not available.");
}

[TestCase(ClassType.Barbarian, 1)]
[TestCase(ClassType.Bard, 1)]
[TestCase(ClassType.Cleric, 1)]
[TestCase(ClassType.Druid, 1)]
[TestCase(ClassType.Fighter, 1)]
[TestCase(ClassType.Ranger, 1)]
[TestCase(ClassType.Rogue, 1)]
[TestCase(ClassType.Sorcerer, 1)]
[TestCase(ClassType.Wizard, 1)]
[TestCase(ClassType.Aberration, 2)]
[TestCase(ClassType.Animal, 2)]
[TestCase(ClassType.Construct, 2)]
[TestCase(ClassType.Humanoid, 2)]
[TestCase(ClassType.Monstrous, 2)]
[TestCase(ClassType.Elemental, 2)]
[TestCase(ClassType.Fey, 2)]
[TestCase(ClassType.Dragon, 2)]
[TestCase(ClassType.Undead, 2)]
[TestCase(ClassType.Commoner, 2)]
[TestCase(ClassType.Beast, 2)]
[TestCase(ClassType.Giant, 2)]
[TestCase(ClassType.MagicalBeast, 2)]
[TestCase(ClassType.Outsider, 2)]
[TestCase(ClassType.Shapechanger, 2)]
[TestCase(ClassType.Vermin, 2)]
public void LevelUpCreatureUpdatesStats(ClassType classType, int levels)
{
Location startLocation = NwModule.Instance.StartingLocation;
NwCreature? creature = NwCreature.Create(StandardResRef.Creature.nw_bandit002, startLocation);
NwClass? nwClass = NwClass.FromClassType(classType);

Assert.That(creature, Is.Not.Null, "Creature was null after creation.");
Assert.That(nwClass, Is.Not.Null, "Class was null after creation.");
Assert.That(creature!.IsValid, Is.True, "Creature was invalid after creation.");

createdTestObjects.Add(creature);
CreatureClassInfo? classInfoBefore = creature.GetClassInfo(nwClass);
int classLevels = 0;

if (classInfoBefore != null)
{
classLevels = classInfoBefore.Level;
}

creature.LevelUp(nwClass!, levels);

CreatureClassInfo? classInfo = creature.GetClassInfo(nwClass);
Assert.That(classInfo, Is.Not.Null, "Creature did not have class after level up.");
Assert.That(classInfo!.Level, Is.EqualTo(classLevels + levels), "Creature did not receive the correct amount of levels.");
}

[TearDown]
public void CleanupTestObjects()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Anvil.API;
using Anvil.Services;
using NUnit.Framework;

Expand Down Expand Up @@ -42,7 +41,6 @@ public void InjectionServiceInjectsInstanceDependencies()
[Test(Description = "Services with inject properties are implicitly injected.")]
public void InjectionServiceInjectsServiceDependencies()
{
Assert.That(StaticInjectionTestService?.NwServer, Is.Not.Null, "A constructor dependency was not injected.");
Assert.That(StaticInjectionTestService?.ResourceManager, Is.Not.Null, "A constructor dependency was not injected.");
Assert.That(StaticInjectionTestService?.ChatService, Is.Null, "A property was injected when it shouldn't have.");
Assert.That(StaticInjectionTestService?.EventService, Is.Not.Null, "A property dependency was not injected.");
Expand All @@ -64,8 +62,6 @@ private sealed class InjectionTest
[ServiceBinding(typeof(InjectionTestService))]
internal sealed class InjectionTestService
{
public NwServer? NwServer { get; }

public ResourceManager? ResourceManager { get; }

// Not injected with attribute or initialized from constructor, expected to be null.
Expand All @@ -78,9 +74,8 @@ internal sealed class InjectionTestService
[Inject]
internal HookService? HookService { get; init; }

public InjectionTestService(NwServer nwServer, ResourceManager resourceManager)
public InjectionTestService(ResourceManager resourceManager)
{
NwServer = nwServer;
ResourceManager = resourceManager;
}
}
Expand Down
Loading

0 comments on commit 1f2ccd6

Please sign in to comment.