Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
jhett12321 committed Aug 12, 2022
2 parents 68b7d4f + 6e1259f commit 05ee5f7
Show file tree
Hide file tree
Showing 27 changed files with 859 additions and 292 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ 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.14
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.13...v8193.34.14

### Added
- NuiText: Added `Border` and `Scrollbars` properties.
- ItemProperty: Added properties for referencing 2da data.
- ItemProperty: Added Create() factory method overload using the new table class types.
- NwGameTables: Added `ItemPropertyTable`, `ItemPropertyItemMapTable`, `ItemPropertyCostTables`, `ItemPropertyParamTables`

### Changed
- **BREAKING CHANGE:** TwoDimArrays must now be initialized via `NwGameTables.GetTable`.

### Fixed
- Fixed a server crash when using TwoDimArray after the native array structure was evicted from the 2da cache.
- Fixed updating the weight of a equipped item with `NwItem.Weight` not correctly updating creature weight and encumbrance.
- Fixed `NwBaseItem.ArmorCheckPenalty` returning an unsigned integer.

## 8193.34.13
https://github.com/nwn-dotnet/Anvil/compare/v8193.34.12...v8193.34.13

Expand Down
22 changes: 22 additions & 0 deletions NWN.Anvil.Tests/src/main/API/Nui/Widgets/NuiTextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Anvil.API;
using NUnit.Framework;

namespace Anvil.Tests.API
{
[TestFixture(Category = "API.Nui")]
public sealed class NuiTextTests
{
[Test(Description = "Serializing a NuiText creates a valid JSON structure.")]
public void SerializeNuiDrawListArcReturnsValidJsonStructure()
{
NuiText nuiText = new NuiText("Some Text")
{
Enabled = false,
Border = false,
Scrollbars = NuiScrollbars.Y,
};

Assert.That(JsonUtility.ToJson(nuiText), Is.EqualTo(@"{""value"":""Some Text"",""border"":false,""scrollbars"":2,""type"":""text"",""enabled"":false}"));
}
}
}
20 changes: 10 additions & 10 deletions NWN.Anvil.Tests/src/main/API/TwoDimArray/TwoDimArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 1 Test1 ""Test 1"" 0x1 1.0f
ResourceManager.WriteTempResource(resourceName, StringHelper.Cp1252Encoding.GetBytes(twoDimArray));
createdTempResources.Add(resourceName);

TwoDimArray array = new TwoDimArray(resourceName);
TwoDimArray array = NwGameTables.GetTable(resourceName);

Assert.That(array.RowCount, Is.EqualTo(3));
Assert.That(array.ColumnCount, Is.EqualTo(4));
Expand All @@ -52,42 +52,42 @@ public void Invalid2daThrowsException()
{
Assert.That(() =>
{
TwoDimArray _ = new TwoDimArray("invalidtest");
TwoDimArray _ = NwGameTables.GetTable("invalidtest");
}, Throws.Exception.TypeOf<ArgumentException>());
}

[Test(Description = "2da arrays are equal if they reference the same pointer.")]
public void Same2daIsConsideredEqual()
{
TwoDimArray array1 = new TwoDimArray("appearance.2da");
TwoDimArray array2 = new TwoDimArray("appearance.2da");
TwoDimArray array1 = NwGameTables.GetTable("appearance.2da");
TwoDimArray array2 = NwGameTables.GetTable("appearance.2da");

Assert.That(array1, Is.EqualTo(array2));
}

[Test(Description = "2da arrays are equal if they reference the same pointer.")]
public void Same2daGenericIsConsideredEqual()
{
TwoDimArray<AppearanceTableEntry> array1 = new TwoDimArray<AppearanceTableEntry>("appearance.2da");
TwoDimArray<AppearanceTableEntry> array2 = new TwoDimArray<AppearanceTableEntry>("appearance.2da");
TwoDimArray<AppearanceTableEntry> array1 = NwGameTables.GetTable<AppearanceTableEntry>("appearance.2da");
TwoDimArray<AppearanceTableEntry> array2 = NwGameTables.GetTable<AppearanceTableEntry>("appearance.2da");

Assert.That(array1, Is.EqualTo(array2));
}

[Test(Description = "2da arrays are equal if they reference the same pointer.")]
public void Same2daMixedIsConsideredEqual()
{
TwoDimArray array1 = new TwoDimArray("appearance.2da");
TwoDimArray<AppearanceTableEntry> array2 = new TwoDimArray<AppearanceTableEntry>("appearance.2da");
TwoDimArray array1 = NwGameTables.GetTable("appearance.2da");
TwoDimArray<AppearanceTableEntry> array2 = NwGameTables.GetTable<AppearanceTableEntry>("appearance.2da");

Assert.That(array1, Is.EqualTo(array2));
}

[Test(Description = "2da arrays are not equal if they do not reference the same pointer.")]
public void Different2daAreNotConsideredEqual()
{
TwoDimArray array1 = new TwoDimArray<AppearanceTableEntry>("appearance.2da");
TwoDimArray array2 = new TwoDimArray<AppearanceTableEntry>("environment.2da");
TwoDimArray array1 = NwGameTables.GetTable<AppearanceTableEntry>("appearance.2da");
TwoDimArray array2 = NwGameTables.GetTable<EnvironmentPreset>("environment.2da");

Assert.That(array1, Is.Not.EqualTo(array2));
}
Expand Down
2 changes: 2 additions & 0 deletions NWN.Anvil.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/TRAILING_COMMA_IN_MULTILINE_LISTS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_STATEMENT_CONDITIONS/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_TYPE/@EntryValue">0</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_INSIDE_REGION/@EntryValue">0</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_BRACES_INSIDE_STATEMENT_CONDITIONS/@EntryValue">False</s:Boolean>
Expand Down Expand Up @@ -718,6 +719,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CustomTools/CustomToolsData/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
Expand Down
5 changes: 5 additions & 0 deletions NWN.Anvil/src/main/API/EngineStructure/ItemProperty.Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public static ItemProperty Custom(int type, int subType = -1, int costTableValue
return NWScript.ItemPropertyCustom(type, subType, costTableValue, param1Value)!;
}

public static ItemProperty Custom(ItemPropertyTableEntry property, ItemPropertySubTypeTableEntry? subType = null, ItemPropertyCostTableEntry? costTableValue = null, ItemPropertyParamTableEntry? paramTableValue = null)
{
return NWScript.ItemPropertyCustom(property.RowIndex, subType?.RowIndex ?? -1, costTableValue?.RowIndex ?? -1, paramTableValue?.RowIndex ?? -1)!;
}

public static ItemProperty DamageBonus(IPDamageType damageType, IPDamageBonus damageBonus)
{
return NWScript.ItemPropertyDamageBonus((int)damageType, (int)damageBonus)!;
Expand Down
104 changes: 73 additions & 31 deletions NWN.Anvil/src/main/API/EngineStructure/ItemProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,39 @@ public sealed partial class ItemProperty : EffectBase
internal ItemProperty(CGameEffect effect, bool memoryOwn) : base(effect, memoryOwn) {}

/// <summary>
/// Gets or sets the cost table to use for this item property.<br/>
/// See "iprp_costtable.2da" for a list of available tables.
/// Gets the cost table used by this item property.
/// </summary>
public int CostTable
public TwoDimArray<ItemPropertyCostTableEntry>? CostTable
{
get => Effect.GetInteger(2);
set => Effect.SetInteger(2, value);
get
{
int tableIndex = Effect.GetInteger(2);
if (tableIndex >= 0 && tableIndex < NwGameTables.ItemPropertyCostTables.Count)
{
return NwGameTables.ItemPropertyCostTables[tableIndex].Table;
}

return null;
}
}

/// <summary>
/// Gets or sets the cost table entry to use for this item property.<br/>
/// This is the row index inside the set <see cref="CostTable"/>.
/// Gets or sets the cost table entry that is set for this item property.<br/>
/// </summary>
public int CostTableValue
public ItemPropertyCostTableEntry? CostTableValue
{
get => Effect.GetInteger(3);
set => Effect.SetInteger(3, value);
get
{
int tableIndex = Effect.GetInteger(3);
TwoDimArray<ItemPropertyCostTableEntry>? table = CostTable;
if (tableIndex >= 0 && table != null && tableIndex < table.Count)
{
return table[tableIndex];
}

return null;
}
set => Effect.SetInteger(3, value?.RowIndex ?? -1);
}

/// <summary>
Expand All @@ -38,47 +54,73 @@ public EffectDuration DurationType
}

/// <summary>
/// Gets or sets the "param1" table to use for this item property.<br/>
/// See "iprp_paramtable.2da" for a list of available tables.
/// Gets the #1 param table used by this item property.
/// </summary>
public int Param1Table
public TwoDimArray<ItemPropertyParamTableEntry>? Param1Table
{
get => Effect.GetInteger(4);
set => Effect.SetInteger(4, value);
get
{
int tableIndex = Effect.GetInteger(4);
if (tableIndex >= 0 && tableIndex < NwGameTables.ItemPropertyParamTables.Count)
{
return NwGameTables.ItemPropertyParamTables[tableIndex].Table;
}

return null;
}
}

/// <summary>
/// Gets or sets the "param1" table entry to use for this item property.<br/>
/// This is the row index inside the set <see cref="Param1Table"/>.
/// Gets or sets the #1 param table entry that is set for this item property.<br/>
/// </summary>
public int Param1TableValue
public ItemPropertyParamTableEntry? Param1TableValue
{
get => Effect.GetInteger(5);
set => Effect.SetInteger(5, value);
get
{
int tableIndex = Effect.GetInteger(5);
TwoDimArray<ItemPropertyParamTableEntry>? table = Param1Table;

if (tableIndex >= 0 && table != null && tableIndex < table.Count)
{
return table[tableIndex];
}

return null;
}
set => Effect.SetInteger(5, value?.RowIndex ?? -1);
}

[Obsolete("Use Property.PropertyType instead.")]
public ItemPropertyType PropertyType => Property.PropertyType;

/// <summary>
/// Gets or sets the type of this item property (as defined in itempropdef.2da).
/// Gets the base item property used to create this item property.
/// </summary>
public ItemPropertyType PropertyType
{
get => (ItemPropertyType)Effect.GetInteger(0);
set => Effect.SetInteger(0, (int)value);
}
public ItemPropertyTableEntry Property => NwGameTables.ItemPropertyTable[Effect.GetInteger(0)];

/// <summary>
/// Gets the remaining duration until the item property expires (if this item property is temporary). Otherwise, returns <see cref="TimeSpan.Zero"/>.
/// </summary>
public TimeSpan RemainingDuration => TimeSpan.FromSeconds(NWScript.GetItemPropertyDurationRemaining(this));

/// <summary>
/// Gets or sets the SubType index for this item property.<br/>
/// The mapping of this value can be found by first finding the 2da name in itempropdef.2da under the "SubTypeResRef" column, then locating the index of the subtype in the specified 2da.
/// Gets or sets the sub type that is set on this item property.
/// </summary>
public int SubType
public ItemPropertySubTypeTableEntry? SubType
{
get => Effect.GetInteger(1);
set => Effect.SetInteger(1, value);
get
{
int tableIndex = Effect.GetInteger(1);
TwoDimArray<ItemPropertySubTypeTableEntry>? table = Property.SubTypeTable;

if (tableIndex >= 0 && table != null && tableIndex < table.Count)
{
return table[tableIndex];
}

return null;
}
set => Effect.SetInteger(1, value?.RowIndex ?? -1);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public OnPlayerEquipItem()
{
// Patch player reference due to a reference bug during client enter context
// See https://github.com/Beamdog/nwn-issues/issues/367
if (Player is null && Item.Possessor is NwCreature creature)
if (Player is null && Item?.Possessor is NwCreature creature)
{
Player = creature;
}
Expand All @@ -28,7 +28,11 @@ public OnPlayerEquipItem()
/// <summary>
/// Gets the last equipped <see cref="NwItem"/> that triggered the event.
/// </summary>
public NwItem Item { get; } = NWScript.GetPCItemLastEquipped().ToNwObject<NwItem>()!;
/// <remarks>
/// Nullable because of an edge case where the item is invalid on logout while polymorphed.
/// See https://github.com/Beamdog/nwn-issues/issues/464
/// </remarks>
public NwItem? Item { get; } = NWScript.GetPCItemLastEquipped().ToNwObject<NwItem>();

/// <summary>
/// Gets the <see cref="NwCreature"/> that last equipped <see cref="NwItem"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ public sealed class OnChatMessageSend : IEvent
/// <summary>
/// Gets the sender of this message.
/// </summary>
public NwObject Sender { get; internal init; } = null!;
/// <remarks>
/// May be null when the message is sent by the server, e.g. with the nwscript function SendMessageToAllDMs.
/// </remarks>
public NwObject? Sender { get; internal init; }

/// <summary>
/// Skips this chat message.
Expand All @@ -35,7 +38,7 @@ public sealed class OnChatMessageSend : IEvent
/// </summary>
public NwPlayer? Target { get; internal init; }

NwObject IEvent.Context => Sender;
NwObject? IEvent.Context => Sender;
}
}

Expand Down
6 changes: 6 additions & 0 deletions NWN.Anvil/src/main/API/Nui/Widgets/NuiText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public NuiText(NuiProperty<string> text)
[JsonProperty("value")]
public NuiProperty<string> Text { get; set; }

[JsonProperty("border")]
public bool Border { get; set; } = true;

[JsonProperty("scrollbars")]
public NuiScrollbars Scrollbars { get; set; } = NuiScrollbars.Auto;

public override string Type => "text";
}
}
26 changes: 25 additions & 1 deletion NWN.Anvil/src/main/API/Object/NwItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public bool Pickpocketable
/// <summary>
/// Gets the GameObject that has this item in its inventory. Returns null if it is on the ground, or not in any inventory.
/// </summary>
/// <remarks>This can be a creature, a placeable, or an item container (e.g magic bag). Use <see cref="RootPossessor"/> to get the root possessor of this item.</remarks>
public NwGameObject? Possessor => NWScript.GetItemPossessor(this).ToNwObject<NwGameObject>();

/// <summary>
Expand Down Expand Up @@ -214,6 +215,24 @@ public string UnidentifiedDescription
set => NWScript.SetDescription(this, value, false.ToInt());
}

/// <summary>
/// Gets the root possessor of this item.
/// </summary>
/// <remarks>If this item is in a container, this is the creature/placeable holding the container that holds this item. Otherwise, this returns the same object as <see cref="Possessor"/>.</remarks>
public NwGameObject? RootPossessor
{
get
{
NwGameObject? possessor = Possessor;
if (possessor is NwItem item)
{
return item.Possessor as NwCreature;
}

return possessor as NwCreature;
}
}

/// <summary>
/// Gets or sets the weight of this item, in pounds.
/// </summary>
Expand All @@ -223,7 +242,12 @@ public decimal Weight
set
{
Item.m_nWeight = (int)Math.Round(value * 10.0m, MidpointRounding.ToZero);
Item.m_oidPossessor.ToNwObject<NwCreature>()?.Creature.UpdateEncumbranceState();
if (RootPossessor is NwCreature creature)
{
creature.Creature.m_nEquippedWeight = creature.Creature.ComputeTotalEquippedWeight();
creature.Creature.m_nTotalWeightCarried = creature.Creature.ComputeTotalWeightCarried();
creature.Creature.UpdateEncumbranceState();
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions NWN.Anvil/src/main/API/Object/NwPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -93,6 +94,7 @@ public float CutsceneCameraMoveRate
/// <summary>
/// Gets if this player is connected and playing (true), or if this player is still on character selection or connecting (false).
/// </summary>
[MemberNotNullWhen(true, nameof(LoginCreature), nameof(ControlledCreature))]
public bool IsConnected => Player.m_oidPCObject != NwObject.Invalid;

/// <summary>
Expand Down
Loading

0 comments on commit 05ee5f7

Please sign in to comment.