Skip to content

Commit 1f2ccd6

Browse files
committed
Merge branch 'development'
2 parents b914efd + d693222 commit 1f2ccd6

File tree

208 files changed

+1908
-445
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

208 files changed

+1908
-445
lines changed

CHANGELOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file.
33

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

6+
## 8193.34.25
7+
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.24...v8193.34.25
8+
9+
### Added
10+
- Exposed `HomeStorage` class for accessing paths in anvil home.
11+
- Added support for creating `Cassowary` solvers through `new Cassowary()`
12+
- NwGameObject: Added `ActionJumpToLocation` method.
13+
- Events: Added `OnPlayerQuickChat` event.
14+
- NwPlayer: Added object name override support (SetObjectNameOverride)
15+
- NwPlayer: Added player-specific looping vfx support (AddLoopingVisualEffect)
16+
- NwCreature: Added `LevelUp` method that bypasses validation.
17+
18+
### Package Updates
19+
- NLog: 5.1.1 -> 5.1.2
20+
- Microsoft.CodeAnalysis.CSharp: 4.4.0 -> 4.5.0
21+
22+
### Changed
23+
- Optional anvil services will now log a message when they are used.
24+
25+
### Fixed
26+
- !! Fixed a memory leak when not using `Dispose()` on engine structures. (Effect, Location, ItemProperty, Json, SQLQuery, Talent)
27+
628
## 8193.34.24
729
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.23...v8193.34.24
830

NWN.Anvil.TestRunner/NWN.Anvil.TestRunner.csproj

+6-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
</PropertyGroup>
5757

5858
<ItemGroup>
59-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
59+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
6060
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
6161
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
6262
</ItemGroup>
@@ -68,6 +68,11 @@
6868
</ProjectReference>
6969
</ItemGroup>
7070

71+
<ItemGroup>
72+
<PackageReference Include="NWN.Core" Version="8193.34.12" PrivateAssets="compile" />
73+
<PackageReference Include="NWN.Native" Version="8193.34.5" PrivateAssets="compile" />
74+
</ItemGroup>
75+
7176
<ItemGroup>
7277
<None Remove="src\lib\nunit\CONTRIBUTING.md" />
7378
<None Remove="src\lib\nunit\CODE_OF_CONDUCT.md" />

NWN.Anvil.TestRunner/src/main/TestRunnerService.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ internal sealed class TestRunnerService
2020
{
2121
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
2222

23-
private readonly NwServer nwServer;
23+
private readonly MainThreadSynchronizationContext mainThreadSynchronizationContext;
2424

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

2828
private Thread testWorkerThread;
2929

30-
public TestRunnerService(PluginStorageService pluginStorageService, NwServer nwServer)
30+
public TestRunnerService(MainThreadSynchronizationContext mainThreadSynchronizationContext, PluginStorageService pluginStorageService)
3131
{
32-
this.nwServer = nwServer;
32+
this.mainThreadSynchronizationContext = mainThreadSynchronizationContext;
3333

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

5050
private void OnModuleLoad(ModuleEvents.OnModuleLoad eventData)
5151
{
52-
TestCommand.DefaultSynchronizationContext = NwTask.MainThreadSynchronizationContext;
52+
TestCommand.DefaultSynchronizationContext = mainThreadSynchronizationContext;
5353
testWorkerThread = new Thread(RunTests);
5454
testWorkerThread.Start();
5555
}
@@ -72,7 +72,7 @@ private async void Shutdown()
7272
{
7373
testWorkerThread = null;
7474
await NwTask.SwitchToMainThread();
75-
nwServer.ShutdownServer();
75+
NwServer.Instance.ShutdownServer();
7676
}
7777

7878
private string[] GetRunnerArguments(Assembly assembly)

NWN.Anvil.Tests/NWN.Anvil.Tests.csproj.DotSettings

+3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain/@EntryIndexedValue">True</s:Boolean>
33
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Casync/@EntryIndexedValue">True</s:Boolean>
44
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cenginestructure/@EntryIndexedValue">True</s:Boolean>
5+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cenginestructures/@EntryIndexedValue">True</s:Boolean>
56
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui/@EntryIndexedValue">True</s:Boolean>
67
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Cbindings/@EntryIndexedValue">True</s:Boolean>
78
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Clayout/@EntryIndexedValue">True</s:Boolean>
89
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cnui_005Cwidgets/@EntryIndexedValue">True</s:Boolean>
910
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cobject/@EntryIndexedValue">True</s:Boolean>
11+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cobjects/@EntryIndexedValue">True</s:Boolean>
1012
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Ctlk/@EntryIndexedValue">True</s:Boolean>
1113
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Ctwodimarray/@EntryIndexedValue">True</s:Boolean>
1214
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cutils/@EntryIndexedValue">True</s:Boolean>
1315
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cvariable/@EntryIndexedValue">True</s:Boolean>
16+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Capi_005Cvariables/@EntryIndexedValue">True</s:Boolean>
1417
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cresources/@EntryIndexedValue">True</s:Boolean>
1518
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cscheduler/@EntryIndexedValue">True</s:Boolean>
1619
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cmain_005Cservices_005Cservices/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Anvil.API;
2+
using NUnit.Framework;
3+
4+
namespace Anvil.Tests.API
5+
{
6+
[TestFixture(Category = "API.EngineStructure")]
7+
public sealed class CassowaryTests
8+
{
9+
[Test(Description = "Creating a new cassowary and disposing the cassowary explicitly frees the associated memory.")]
10+
public void CreateAndDisposeCassowaryValidPropertyUpdated()
11+
{
12+
Cassowary cassowary = new Cassowary();
13+
Assert.That(cassowary.IsValid, Is.True, "Cassowary was not valid after creation.");
14+
cassowary.Dispose();
15+
Assert.That(cassowary.IsValid, Is.False, "Cassowary was still valid after disposing.");
16+
}
17+
18+
[Test(Description = "A cassowary correctly finds a solution from a set of constraints.")]
19+
public void CreateCassowaryConstraintReturnsValidSolution()
20+
{
21+
Cassowary cassowary = new Cassowary();
22+
23+
cassowary.AddConstraint("middle == (left + right) / 2");
24+
cassowary.AddConstraint("right == left + 10");
25+
cassowary.AddConstraint("right <= 100");
26+
cassowary.AddConstraint("left >= 0");
27+
28+
Assert.That(cassowary.GetValue("left"), Is.EqualTo(90f));
29+
Assert.That(cassowary.GetValue("middle"), Is.EqualTo(95f));
30+
Assert.That(cassowary.GetValue("right"), Is.EqualTo(100f));
31+
}
32+
33+
[Test(Description = "A cassowary correctly finds a solution from a set of constraints and a suggested value.")]
34+
public void CreateCassowaryConstraintWithSuggestedValueReturnsValidSolution()
35+
{
36+
Cassowary cassowary = new Cassowary();
37+
38+
cassowary.AddConstraint("middle == (left + right) / 2");
39+
cassowary.AddConstraint("right == left + 10");
40+
cassowary.AddConstraint("right <= 100");
41+
cassowary.AddConstraint("left >= 0");
42+
43+
cassowary.SuggestValue("middle", 45f);
44+
45+
Assert.That(cassowary.GetValue("left"), Is.EqualTo(40f));
46+
Assert.That(cassowary.GetValue("middle"), Is.EqualTo(45f));
47+
Assert.That(cassowary.GetValue("right"), Is.EqualTo(50f));
48+
}
49+
}
50+
}

NWN.Anvil.Tests/src/main/API/EngineStructure/EffectTests.cs NWN.Anvil.Tests/src/main/API/EngineStructures/EffectTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Anvil.Tests.API
88
public sealed class EffectTests
99
{
1010
[Test(Description = "Creating an effect and disposing the effect explicitly frees the associated memory.")]
11-
public void CreateAndDisposeEffectValidPropertyUpdated()
11+
public void CreateAndDisposeEffectFreesNativeStructure()
1212
{
1313
Effect effect = Effect.CutsceneParalyze();
1414
Assert.That(effect.IsValid, Is.True, "Effect was not valid after creation.");
@@ -17,7 +17,7 @@ public void CreateAndDisposeEffectValidPropertyUpdated()
1717
}
1818

1919
[Test(Description = "A soft effect reference created from a native object does not cause the original effect to be deleted.")]
20-
public void CreateSoftEffectReferencAndDisposeDoesNotFreeMemory()
20+
public void CreateSoftEffectReferenceAndDisposeDoesNotFreeMemory()
2121
{
2222
Effect effect = Effect.Blindness();
2323
Assert.That(effect.IsValid, Is.True, "Effect was not valid after creation.");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Anvil.API;
2+
using NUnit.Framework;
3+
using NWN.Native.API;
4+
using ItemProperty = Anvil.API.ItemProperty;
5+
6+
namespace Anvil.Tests.API
7+
{
8+
[TestFixture(Category = "API.EngineStructure")]
9+
public sealed class ItemPropertyTests
10+
{
11+
[Test(Description = "Creating an item property and disposing the item property explicitly frees the associated memory.")]
12+
public void CreateAndDisposeItemPropertyFreesNativeStructure()
13+
{
14+
ItemProperty itemProperty = ItemProperty.Haste();
15+
Assert.That(itemProperty.IsValid, Is.True, "Item property was not valid after creation.");
16+
itemProperty.Dispose();
17+
Assert.That(itemProperty.IsValid, Is.False, "Item property was still valid after disposing.");
18+
}
19+
20+
[Test(Description = "A soft item property reference created from a native object does not cause the original item property to be deleted.")]
21+
public void CreateSoftItemPropertyReferenceAndDisposeDoesNotFreeMemory()
22+
{
23+
ItemProperty itemProperty = ItemProperty.Haste();
24+
Assert.That(itemProperty.IsValid, Is.True, "Item property was not valid after creation.");
25+
26+
CGameEffect gameEffect = itemProperty;
27+
Assert.That(gameEffect, Is.Not.Null, "Native Item property was not valid after implicit cast.");
28+
29+
ItemProperty softReference = gameEffect.ToItemProperty(false)!;
30+
softReference.Dispose();
31+
Assert.That(softReference.IsValid, Is.True, "The soft reference disposed the memory of the original item property.");
32+
}
33+
}
34+
}

NWN.Anvil.Tests/src/main/API/Object/NwAreaTests.cs NWN.Anvil.Tests/src/main/API/Objects/NwAreaTests.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void ApplyEnvironmentPresetIsApplied(int presetRowIndex)
3838
{
3939
EnvironmentPreset preset = NwGameTables.EnvironmentPresetTable.GetRow(presetRowIndex);
4040

41-
NwArea area = NwModule.Instance.StartingLocation.Area!;
41+
NwArea area = NwModule.Instance.StartingLocation.Area;
4242
area.ApplyEnvironmentPreset(preset);
4343

4444
Assert.That(area.DayNightMode, Is.EqualTo(preset.DayNightMode));
@@ -66,7 +66,7 @@ public void ApplyEnvironmentPresetIsApplied(int presetRowIndex)
6666
[TestCase(AreaFlags.UnderGround)]
6767
public void ChangeAreaFlagsUpdatesAreaFlags(AreaFlags flag)
6868
{
69-
NwArea area = NwModule.Instance.StartingLocation.Area!;
69+
NwArea area = NwModule.Instance.StartingLocation.Area;
7070
area.AreaFlags |= flag;
7171
Assert.That(area.AreaFlags.HasFlag(flag), Is.EqualTo(true));
7272
area.AreaFlags &= ~flag;
@@ -76,7 +76,7 @@ public void ChangeAreaFlagsUpdatesAreaFlags(AreaFlags flag)
7676
[Test(Description = "Changing the IsInterior value correctly updates the area flag.")]
7777
public void ChangeAreaInteriorUpdatesAreaFlags()
7878
{
79-
NwArea area = NwModule.Instance.StartingLocation.Area!;
79+
NwArea area = NwModule.Instance.StartingLocation.Area;
8080
area.IsInterior = true;
8181
area.IsAboveGround = true; // NWScript.GetIsAreaInterior() always returns true if this flag is set to false.
8282

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

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

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

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

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

173173
area.IsUnderGround = true;
174174
Assert.That(area.IsUnderGround, Is.EqualTo(true));

NWN.Anvil.Tests/src/main/API/Object/NwCreatureTests.cs NWN.Anvil.Tests/src/main/API/Objects/NwCreatureTests.cs

+51
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,57 @@ public void SetFeatRemainingUsesCorrectlyUpdatesState(byte uses)
261261
Assert.That(creature.HasFeatPrepared(feat!), Is.EqualTo(uses > 0), "Creature incorrectly assumes the feat is/is not available.");
262262
}
263263

264+
[TestCase(ClassType.Barbarian, 1)]
265+
[TestCase(ClassType.Bard, 1)]
266+
[TestCase(ClassType.Cleric, 1)]
267+
[TestCase(ClassType.Druid, 1)]
268+
[TestCase(ClassType.Fighter, 1)]
269+
[TestCase(ClassType.Ranger, 1)]
270+
[TestCase(ClassType.Rogue, 1)]
271+
[TestCase(ClassType.Sorcerer, 1)]
272+
[TestCase(ClassType.Wizard, 1)]
273+
[TestCase(ClassType.Aberration, 2)]
274+
[TestCase(ClassType.Animal, 2)]
275+
[TestCase(ClassType.Construct, 2)]
276+
[TestCase(ClassType.Humanoid, 2)]
277+
[TestCase(ClassType.Monstrous, 2)]
278+
[TestCase(ClassType.Elemental, 2)]
279+
[TestCase(ClassType.Fey, 2)]
280+
[TestCase(ClassType.Dragon, 2)]
281+
[TestCase(ClassType.Undead, 2)]
282+
[TestCase(ClassType.Commoner, 2)]
283+
[TestCase(ClassType.Beast, 2)]
284+
[TestCase(ClassType.Giant, 2)]
285+
[TestCase(ClassType.MagicalBeast, 2)]
286+
[TestCase(ClassType.Outsider, 2)]
287+
[TestCase(ClassType.Shapechanger, 2)]
288+
[TestCase(ClassType.Vermin, 2)]
289+
public void LevelUpCreatureUpdatesStats(ClassType classType, int levels)
290+
{
291+
Location startLocation = NwModule.Instance.StartingLocation;
292+
NwCreature? creature = NwCreature.Create(StandardResRef.Creature.nw_bandit002, startLocation);
293+
NwClass? nwClass = NwClass.FromClassType(classType);
294+
295+
Assert.That(creature, Is.Not.Null, "Creature was null after creation.");
296+
Assert.That(nwClass, Is.Not.Null, "Class was null after creation.");
297+
Assert.That(creature!.IsValid, Is.True, "Creature was invalid after creation.");
298+
299+
createdTestObjects.Add(creature);
300+
CreatureClassInfo? classInfoBefore = creature.GetClassInfo(nwClass);
301+
int classLevels = 0;
302+
303+
if (classInfoBefore != null)
304+
{
305+
classLevels = classInfoBefore.Level;
306+
}
307+
308+
creature.LevelUp(nwClass!, levels);
309+
310+
CreatureClassInfo? classInfo = creature.GetClassInfo(nwClass);
311+
Assert.That(classInfo, Is.Not.Null, "Creature did not have class after level up.");
312+
Assert.That(classInfo!.Level, Is.EqualTo(classLevels + levels), "Creature did not receive the correct amount of levels.");
313+
}
314+
264315
[TearDown]
265316
public void CleanupTestObjects()
266317
{

NWN.Anvil.Tests/src/main/Services/Services/InjectionServiceTests.cs

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Anvil.API;
21
using Anvil.Services;
32
using NUnit.Framework;
43

@@ -42,7 +41,6 @@ public void InjectionServiceInjectsInstanceDependencies()
4241
[Test(Description = "Services with inject properties are implicitly injected.")]
4342
public void InjectionServiceInjectsServiceDependencies()
4443
{
45-
Assert.That(StaticInjectionTestService?.NwServer, Is.Not.Null, "A constructor dependency was not injected.");
4644
Assert.That(StaticInjectionTestService?.ResourceManager, Is.Not.Null, "A constructor dependency was not injected.");
4745
Assert.That(StaticInjectionTestService?.ChatService, Is.Null, "A property was injected when it shouldn't have.");
4846
Assert.That(StaticInjectionTestService?.EventService, Is.Not.Null, "A property dependency was not injected.");
@@ -64,8 +62,6 @@ private sealed class InjectionTest
6462
[ServiceBinding(typeof(InjectionTestService))]
6563
internal sealed class InjectionTestService
6664
{
67-
public NwServer? NwServer { get; }
68-
6965
public ResourceManager? ResourceManager { get; }
7066

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

81-
public InjectionTestService(NwServer nwServer, ResourceManager resourceManager)
77+
public InjectionTestService(ResourceManager resourceManager)
8278
{
83-
NwServer = nwServer;
8479
ResourceManager = resourceManager;
8580
}
8681
}

0 commit comments

Comments
 (0)