From a706ef144949a9265b8358ab23ebb91b63074ded Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Thu, 2 Jul 2026 22:35:37 -0700
Subject: [PATCH] fix(ci): run test projects on CI; remove brittle OPL
attribute tests (#2513)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Problem
Three coupled issues, each hiding the next:
1. **CI passed despite failing tests, with no test logs.** ([example run](https://github.com/modernuo/ModernUO/actions/runs/28639143286/job/84931544255) — the `Test` step produced zero output and the job went green.)
2. **Two `EmitsLowerStatReqWhenPassed` tests** fail with `KeyNotFoundException: '1060435'`.
3. Once CI actually ran the tests, **~337 UOContent tests failed** with `FileNotFoundException: tiledata.mul was not found` — the test bootstrap force-loaded copyrighted client data that CI doesn't have.
## Root causes & fixes
### 1. CI ran zero tests (`fix(ci)`)
The `Test` step ran `dotnet test --no-restore`, but the `Build` step only restores/builds `Application` — never the test projects. Without a restore, the test projects have no `project.assets.json`, so `Microsoft.NET.Test.Sdk`'s targets aren't imported, they aren't recognized as test projects, and `dotnet test` runs the `VSTest` target against **zero** projects → no output, **exit 0**.
- Both jobs now run `dotnet test --logger trx --results-directory ./TestResults` (test projects restore and run) **plus a guard** that fails the job if no `.trx` is produced — a permanent backstop against silent zero-test passes.
### 2. Impossible OPL tests (`fix(ci)` + `test(opl)`)
#2501 deliberately emits `LowerStatReq` (`1060435`) **inline in each item**, not in `GetProperties`. A follow-up "fix" dropped the `lowerStatReq:` argument to make the tests compile but left the assertions expecting `1060435`.
- Removed the two impossible tests, then removed the **entire `Tests/PropertyList/` OPL attribute set** from #2501: these assert exact cliloc/value/order of OPL emission per item base — a one-time proof of the #2501 rewire, now a permanent tax on modding (any admin reorder/value change/added line reddens the build). The one non-trivial case (LowerStatReq) is what just broke, because the test was wrong. Inline emission stays covered by the `BaseArmor`/`BaseClothing` tests.
### 3. Tile-data-dependent tests crashed CI (`test(uocontent)`)
UOContent.Tests' collection-fixture constructor force-loaded `tiledata.mul` unconditionally. On CI (no client files) it threw, and xUnit failed **every test in the collection** with the same error — mostly collateral (packet/scheduler/spawner tests that don't need tile data).
- Mirror Server.Tests' graceful pattern: `TestServerInitializer` probes for `tiledata.mul` and only loads tile/multi data (and runs the tile-dependent configure steps) when present, exposing `TileDataLoaded` so the fixture no longer throws.
- Add a shared `TileDataRequirement.SkipIfMissing()` guard and apply it to exactly the **31** pathfinding/multi/AI tests that genuinely need real tile data (`[SkippableFact]`/`[SkippableTheory]`).
## Verification (all local)
| Scenario | Server.Tests | UOContent.Tests |
|---|---|---|
| **Client data absent (CI)** | 726 pass, 17 skip, **0 fail** | 469 pass, 32 skip, **0 fail** |
| **Client data present (dev)** | 726 pass, 0 skip, **0 fail** | 501 pass, 0 skip, **0 fail** |
- Full `dotnet test` exits **0**; TRX files produced; the no-test guard trips (exit 1) only when zero `.trx` are produced.
---
.github/workflows/build-test.yml | 14 +++-
.../Fixtures/TestServerInitializer.cs | 40 ++++++++--
.../Fixtures/TileDataRequirement.cs | 18 +++++
.../Pathing/BitmapAStarAlgorithmTests.cs | 3 +-
.../Engines/Pathing/Multi/BoatPathTests.cs | 6 +-
.../Pathing/Multi/FoundationRedesignTests.cs | 6 +-
.../Pathing/Multi/HousePathRoutingTests.cs | 6 +-
.../Pathing/Multi/MultiEdgeCaseTests.cs | 12 ++-
.../Pathing/Multi/MultiMaskCacheTests.cs | 12 ++-
.../Pathing/Multi/MultiMaskSynthesisTests.cs | 3 +-
.../Pathing/Multi/MultiSplitRoutingTests.cs | 3 +-
.../Pathing/Multi/MultiWalkabilityTests.cs | 6 +-
.../Engines/Pathing/StepCacheFileTests.cs | 6 +-
.../Pathing/StepCacheLifecycleTests.cs | 9 ++-
.../Engines/Pathing/StepCacheParityTests.cs | 3 +-
.../Engines/Pathing/StepProbeParityTests.cs | 3 +-
.../Tests/Engines/Pathing/StepProbeTests.cs | 9 ++-
.../Tests/Mobiles/AI/ApproachTargetTests.cs | 6 +-
.../AosArmorAttributesPropertiesTests.cs | 71 ------------------
.../AosAttributesPropertiesTests.cs | 75 -------------------
.../AosWeaponAttributesPropertiesTests.cs | 74 ------------------
.../PropertyList/BaseArmorPropertiesTests.cs | 40 ----------
.../BaseClothingPropertiesTests.cs | 38 ----------
.../PropertyList/BaseJewelPropertiesTests.cs | 36 ---------
.../BaseTalismanPropertiesTests.cs | 42 -----------
.../PropertyList/BaseWeaponPropertiesTests.cs | 38 ----------
.../Tests/PropertyList/ItemOplTestHelper.cs | 44 -----------
.../PropertyList/SpellbookPropertiesTests.cs | 42 -----------
Projects/UOContent/Misc/AOS.cs | 5 --
29 files changed, 124 insertions(+), 546 deletions(-)
create mode 100644 Projects/UOContent.Tests/Fixtures/TileDataRequirement.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/AosArmorAttributesPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/AosAttributesPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/AosWeaponAttributesPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/BaseArmorPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/BaseClothingPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/BaseJewelPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/BaseTalismanPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/BaseWeaponPropertiesTests.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/ItemOplTestHelper.cs
delete mode 100644 Projects/UOContent.Tests/Tests/PropertyList/SpellbookPropertiesTests.cs
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 105899543..d61839a1c 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -52,7 +52,12 @@ jobs:
- name: Migration Changes
run: git diff --exit-code ./**/Migrations/*.v*.json
- name: Test
- run: dotnet test --no-restore
+ run: |
+ dotnet test --logger trx --results-directory ./TestResults
+ if [ -z "$(find ./TestResults -name '*.trx' 2>/dev/null)" ]; then
+ echo "::error::No test result files were produced - no test projects ran. Failing to avoid masking failures."
+ exit 1
+ fi
build-linux:
runs-on: ubuntu-latest
@@ -106,4 +111,9 @@ jobs:
- name: Build
run: dotnet run --project Projects/BuildTool -- --config Release --skip-prereqs
- name: Test
- run: dotnet test --no-restore
+ run: |
+ dotnet test --logger trx --results-directory ./TestResults
+ if [ -z "$(find ./TestResults -name '*.trx' 2>/dev/null)" ]; then
+ echo "::error::No test result files were produced - no test projects ran. Failing to avoid masking failures."
+ exit 1
+ fi
diff --git a/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs b/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
index 33bdb3634..076f88141 100644
--- a/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
+++ b/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
@@ -30,6 +30,13 @@ internal static class TestServerInitializer
private static bool _initialized;
private static readonly Lock _lock = new();
+ ///
+ /// True if the UO client tile data was found and loaded. When false (e.g. CI, where the
+ /// copyrighted client files are absent), tile/map/multi-dependent tests must skip rather than
+ /// fail. Guard such tests with Skip.If(!TestServerInitializer.TileDataLoaded, ...).
+ ///
+ public static bool TileDataLoaded { get; private set; }
+
public static void Initialize()
{
lock (_lock)
@@ -62,7 +69,10 @@ internal static class TestServerInitializer
// flags are populated before anything that reads TileData (MultiData, MovementImpl,
// CheckMovement). Without this, TileData.MaxItemValue is 0 at MultiData.Configure()
// time, causing every MCL tile ID to be masked to 0 and stored as ID=0 in Tiles[x][y].
- ForceLoadTileData();
+ // The copyrighted client files are absent on CI; when tiledata.mul is missing we skip
+ // the tile/map/multi-dependent bootstrap and leave TileDataLoaded false so those tests
+ // skip instead of failing the whole collection from the fixture constructor.
+ TileDataLoaded = TryForceLoadTileData();
// Production runs every static Configure() via AssemblyHandler.Invoke("Configure");
// the fixture calls a curated subset, so configure the pathfinding singleton here so
@@ -70,11 +80,15 @@ internal static class TestServerInitializer
// calls Find. ServerConfiguration is already loaded above, so the setting resolves.
BitmapAStarAlgorithm.Configure();
- // Multi component lists (multi.mul / MultiCollection.uop). Production invokes this via
- // AssemblyHandler.Invoke("Configure"); the curated fixture subset must call it so that
- // BaseMulti.Components (MultiData.GetComponents) returns real footprints instead of
- // MultiComponentList.Empty. Required by the Multi pathfinding tests.
- MultiData.Configure();
+ if (TileDataLoaded)
+ {
+ // Multi component lists (multi.mul / MultiCollection.uop). Production invokes this via
+ // AssemblyHandler.Invoke("Configure"); the curated fixture subset must call it so that
+ // BaseMulti.Components (MultiData.GetComponents) returns real footprints instead of
+ // MultiComponentList.Empty. Required by the Multi pathfinding tests. Depends on the
+ // client files, so it only runs when tile data loaded.
+ MultiData.Configure();
+ }
World.Configure();
Timer.Init(0);
@@ -86,14 +100,23 @@ internal static class TestServerInitializer
DecayScheduler.Configure();
Server.Engines.Spawners.SpawnerJsonSerializer.Configure();
- VerifyTrammelTileDataLoaded();
+ if (TileDataLoaded)
+ {
+ VerifyTrammelTileDataLoaded();
+ }
_initialized = true;
}
}
- private static void ForceLoadTileData()
+ private static bool TryForceLoadTileData()
{
+ var tileDataPath = Core.FindDataFile("tiledata.mul", false);
+ if (string.IsNullOrEmpty(tileDataPath) || !File.Exists(tileDataPath))
+ {
+ return false;
+ }
+
var loadMethod = typeof(TileData).GetMethod(
"Load",
BindingFlags.Static | BindingFlags.NonPublic
@@ -105,6 +128,7 @@ internal static class TestServerInitializer
);
}
loadMethod.Invoke(null, null);
+ return true;
}
private static void VerifyTrammelTileDataLoaded()
diff --git a/Projects/UOContent.Tests/Fixtures/TileDataRequirement.cs b/Projects/UOContent.Tests/Fixtures/TileDataRequirement.cs
new file mode 100644
index 000000000..7b9af1f27
--- /dev/null
+++ b/Projects/UOContent.Tests/Fixtures/TileDataRequirement.cs
@@ -0,0 +1,18 @@
+using Xunit;
+
+namespace Server.Tests;
+
+///
+/// Shared guard for tests that require the copyrighted UO client tile/map data (tiledata.mul and
+/// friends), which is absent on CI. Call as the first statement of a
+/// [SkippableFact]/[SkippableTheory] so the test is skipped — not failed — when the
+/// data was not loaded. See .
+///
+internal static class TileDataRequirement
+{
+ public static void SkipIfMissing() =>
+ Skip.If(
+ !TestServerInitializer.TileDataLoaded,
+ "Requires UO client tile data (tiledata.mul); absent on CI."
+ );
+}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
index ab9497729..20d729c0f 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
@@ -130,9 +130,10 @@ public class BitmapAStarAlgorithmTests
blocker.Delete();
}
- [Fact]
+ [SkippableFact]
public void DynamicObstaclePass_RejectsCellOccupiedByImpassableItem()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[1];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/BoatPathTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/BoatPathTests.cs
index a9796de7f..694374757 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/BoatPathTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/BoatPathTests.cs
@@ -17,9 +17,10 @@ public class BoatPathTests
private const int WaterX = 1450;
private const int WaterY = 1770;
- [Fact]
+ [SkippableFact]
public void BoatDeck_HasWalkableSurfaceCells()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
@@ -44,9 +45,10 @@ public class BoatPathTests
}
}
- [Fact]
+ [SkippableFact]
public void BoatDeck_FootprintShape_IsPositionInvariant()
{
+ TileDataRequirement.SkipIfMissing();
// The property Phase 2's local-frame, movement-invariant boat cache must preserve:
// the deck's covered-cell shape (in local coords) is identical at two world positions.
var map = Map.Maps[MapId];
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/FoundationRedesignTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/FoundationRedesignTests.cs
index 3fdb4fa3e..e82520ce9 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/FoundationRedesignTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/FoundationRedesignTests.cs
@@ -17,9 +17,10 @@ public class FoundationRedesignTests
private const int PlaceX = 1500;
private const int PlaceY = 1600;
- [Fact]
+ [SkippableFact]
public void SwappingComponents_ChangesFootprint()
{
+ TileDataRequirement.SkipIfMissing();
var foundation = new SwappableFoundation(0x74); // GuildHouse footprint
try
{
@@ -38,9 +39,10 @@ public class FoundationRedesignTests
}
}
- [Fact]
+ [SkippableFact]
public void RedesignReRegistered_RoutesNewFootprintToLivePath()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/HousePathRoutingTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/HousePathRoutingTests.cs
index 41caa6255..8ecbd0591 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/HousePathRoutingTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/HousePathRoutingTests.cs
@@ -22,9 +22,10 @@ public class HousePathRoutingTests
public WalkerStub() => Body = 0xC9;
}
- [Fact]
+ [SkippableFact]
public void PathAround_NeverTraversesAWallCell()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var prevThreshold = StepCache.Instance.MissPromotionThreshold;
StepCache.Instance.MissPromotionThreshold = 1;
@@ -74,9 +75,10 @@ public class HousePathRoutingTests
}
}
- [Fact]
+ [SkippableFact]
public void Demolish_ReopensCoveredCells()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiEdgeCaseTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiEdgeCaseTests.cs
index eb12df7d6..8aa0b8d19 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiEdgeCaseTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiEdgeCaseTests.cs
@@ -25,9 +25,10 @@ public class MultiEdgeCaseTests
/// GetStaticAndMultiTiles yields tiles from BOTH multis; the synthesizer must still
/// agree with CheckMovement everywhere over the union footprint + halo.
///
- [Fact]
+ [SkippableFact]
public void OverlappingMultis_SynthesizerMatchesCheckMovement()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
@@ -75,9 +76,10 @@ public class MultiEdgeCaseTests
/// blocks the rest — that vacuity is a fixture concern, not a synthesizer divergence (the
/// synthesizer agrees with the oracle at every direction either way).
///
- [Fact]
+ [SkippableFact]
public void BoatOverWater_SynthesizerMatchesCheckMovement()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
@@ -98,9 +100,10 @@ public class MultiEdgeCaseTests
/// re-registration pattern HouseFoundation uses on commit) and assert the synthesizer reads
/// the LIVE, post-redesign Components — i.e. it matches CheckMovement on the NEW shape.
///
- [Fact]
+ [SkippableFact]
public void RedesignedFoundation_SynthesizerMatchesNewFootprint()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
@@ -136,10 +139,11 @@ public class MultiEdgeCaseTests
/// dungeon cave-wall corner; add InlineData rows here — the assertion already covers them by
/// construction. (Left intentionally unhunted: do not invent tree/dungeon coords.)
///
- [Theory]
+ [SkippableTheory]
[InlineData(MapId, HouseX, HouseY)] // known-good open Trammel placement (passes today)
public void UserSuppliedScenarios_SynthesizerMatchesCheckMovement(int mapId, int x, int y)
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[mapId];
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskCacheTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskCacheTests.cs
index 8a544897c..7487ce8de 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskCacheTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskCacheTests.cs
@@ -12,9 +12,10 @@ public class MultiMaskCacheTests
private const int PlaceX = 1480;
private const int PlaceY = 1620;
- [Fact]
+ [SkippableFact]
public void TryResolveCoveringMulti_FindsPlacedMulti_AndLocalIndices()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
map.GetAverageZ(PlaceX, PlaceY, out _, out var z, out _);
@@ -38,9 +39,10 @@ public class MultiMaskCacheTests
}
}
- [Fact]
+ [SkippableFact]
public void IsInteriorLocalCell_TrueDeepInside_FalseAtEdge()
{
+ TileDataRequirement.SkipIfMissing();
var multi = new TestMulti(GuildHouseId);
try
{
@@ -122,9 +124,10 @@ public class MultiMaskCacheTests
Assert.False(MultiMaskCache.TerrainTopBelow(map, PlaceX, PlaceY, (sbyte)(ground - 50)));
}
- [Fact]
+ [SkippableFact]
public void PathThroughHouseInterior_IncrementsMultiMaskCacheHits()
{
+ TileDataRequirement.SkipIfMissing();
// (PlaceX,PlaceY)=(1480,1620) is cluttered (footprint overlaps tall map statics → dirty), so it
// would never serve the interior cache under the footprint-clean gate. Use a known flat/clear
// spot so a house there is footprint-clean and its interior cells serve from the cache.
@@ -242,9 +245,10 @@ public class MultiMaskCacheTests
}
}
- [Fact]
+ [SkippableFact]
public void ComputeFootprintClean_TrueAtNormalPlacement_FalseWhenSunk()
{
+ TileDataRequirement.SkipIfMissing();
// (PlaceX,PlaceY) is a cluttered spot whose footprint overlaps tall map statics, so a guild
// house there is never footprint-clean. Use a known flat/clear spot for the clean assertion.
const int CleanX = 1560;
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskSynthesisTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskSynthesisTests.cs
index 923784f4f..b415ec3d6 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskSynthesisTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiMaskSynthesisTests.cs
@@ -11,9 +11,10 @@ public class MultiMaskSynthesisTests
private const int PlaceX = 1480;
private const int PlaceY = 1620;
- [Fact]
+ [SkippableFact]
public void ComputeMultiMaskAt_MatchesCheckMovement_OverFootprintAndHalo()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
map.GetAverageZ(PlaceX, PlaceY, out _, out var z, out _);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiSplitRoutingTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiSplitRoutingTests.cs
index 4775b8fac..1103cdb83 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiSplitRoutingTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiSplitRoutingTests.cs
@@ -17,9 +17,10 @@ public class MultiSplitRoutingTests
private const int PlaceX = 1500;
private const int PlaceY = 1600;
- [Fact]
+ [SkippableFact]
public void PlacedMulti_RoutesFootprintAndHalo_ToFallthroughMulti()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiWalkabilityTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiWalkabilityTests.cs
index 59e583ac6..d8d46e7f8 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiWalkabilityTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/Multi/MultiWalkabilityTests.cs
@@ -23,9 +23,10 @@ public class MultiWalkabilityTests
public WalkerStub() => Body = 0xC9;
}
- [Fact]
+ [SkippableFact]
public void WallCell_CannotBeEnteredFromAnyAdjacentCell()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
map.GetAverageZ(PlaceX, PlaceY, out _, out var z, out _);
@@ -86,9 +87,10 @@ public class MultiWalkabilityTests
}
}
- [Fact]
+ [SkippableFact]
public void FloorCell_IsStandable()
{
+ TileDataRequirement.SkipIfMissing();
StepCache.Instance.Clear();
var map = Map.Maps[MapId];
map.GetAverageZ(PlaceX, PlaceY, out _, out var z, out _);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
index 0d865d08d..1ef3aa8ab 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
@@ -347,9 +347,10 @@ public class StepCacheFileTests
/// to which proves the
/// default lazy behavior.
///
- [Fact]
+ [SkippableFact]
public void TryOpenLazyReader_WithPreloadFlag_MaterializesAllChunksImmediately()
{
+ TileDataRequirement.SkipIfMissing();
var cache = StepCache.Instance;
cache.Clear();
cache.MissPromotionThreshold = 1;
@@ -401,9 +402,10 @@ public class StepCacheFileTests
}
}
- [Fact]
+ [SkippableFact]
public void LazyReaderHit_BypassesMissTrackerOnFirstTouch()
{
+ TileDataRequirement.SkipIfMissing();
var cache = StepCache.Instance;
cache.Clear();
cache.MissPromotionThreshold = 1; // eager build for save phase
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
index 244c51534..436f78c7e 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
@@ -54,9 +54,10 @@ public class StepCacheLifecycleTests
Assert.Equal(1L, stats.FallthroughNotBuilt);
}
- [Fact]
+ [SkippableFact]
public void TryGetMask_SecondTouchWithinWindow_PromotesAndBuilds()
{
+ TileDataRequirement.SkipIfMissing();
var cache = StepCache.Instance;
cache.Clear();
cache.MissPromotionThreshold = 2;
@@ -112,9 +113,10 @@ public class StepCacheLifecycleTests
Assert.Equal(2L, cache.GetStats().FallthroughNotBuilt);
}
- [Fact]
+ [SkippableFact]
public void TryGetMask_MultipleCallsInSameFindGeneration_StayInFallthrough()
{
+ TileDataRequirement.SkipIfMissing();
var cache = StepCache.Instance;
cache.Clear();
cache.MissPromotionThreshold = 2;
@@ -207,9 +209,10 @@ public class StepCacheLifecycleTests
Assert.Equal((byte)0, lookup.WalkMask);
}
- [Fact]
+ [SkippableFact]
public void MultiCoveredCell_AndHalo_RouteToFallthrough()
{
+ TileDataRequirement.SkipIfMissing();
var cache = StepCache.Instance;
cache.Clear();
cache.MissPromotionThreshold = 1; // eager build so a multi-free cell serves immediately
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
index 149a9fe13..0dc1eb12f 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
@@ -111,9 +111,10 @@ public class StepCacheParityTests
/// (Atlantic coast) and asserts at least one cell has a non-zero WetMask. Catches the
/// failure mode where StepProbe silently bakes zero swim output everywhere.
///
- [Fact]
+ [SkippableFact]
public void SwimBake_ProducesWetCells_OnKnownWaterRegion()
{
+ TileDataRequirement.SkipIfMissing();
var map = Map.Maps[1];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
index df8ff5da5..429d60ca7 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
@@ -14,11 +14,12 @@ public class StaticWalkabilityParityTests
_output = output;
}
- [Theory]
+ [SkippableTheory]
[InlineData("britain_inn_dense", 1480, 1610, 32)]
[InlineData("trammel_open_plain", 1500, 1600, 32)]
public void BakerMatchesCheckMovement(string label, int xStart, int yStart, int size)
{
+ TileDataRequirement.SkipIfMissing();
var map = Map.Maps[1];
Assert.NotNull(map);
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeTests.cs
index cfca235da..9a32d66bf 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeTests.cs
@@ -124,9 +124,10 @@ public class StepProbeTests
Assert.True(found, "Expected at least one fully-flat open cell in (1500..1532, 1600..1632)");
}
- [Fact]
+ [SkippableFact]
public void ComputeMaskAt_BritainInnDense_HasCellWithBlockedDirections()
{
+ TileDataRequirement.SkipIfMissing();
// Invariant: inside the dense Britain inn region, at least one cell must have
// at least one direction blocked by a static. Protects against a regression
// where the baker reports everything as walkable (the original false-pass bug).
@@ -181,9 +182,10 @@ public class StepProbeTests
}
}
- [Fact]
+ [SkippableFact]
public void ComputeMaskAt_PinnedCell_TrammelOpenPlainOrigin()
{
+ TileDataRequirement.SkipIfMissing();
// PINNING test: locks specific output for Trammel (1500, 1600).
// Cell at z=10 with mask 0xC1 (N + W + NW only walkable). The other five
// directions are blocked by water (E/SE/S/SW are wet tiles, NE is shore).
@@ -214,9 +216,10 @@ public class StepProbeTests
Assert.Equal((sbyte)10, result.GetWalkZ(Direction.Up));
}
- [Fact]
+ [SkippableFact]
public void ComputeMaskAt_PinnedCell_BritainInnDenseOrigin()
{
+ TileDataRequirement.SkipIfMissing();
// PINNING test: locks specific output for Trammel (1480, 1610).
// Cell at z=20 with mask 0x3F (N/NE/E/SE/S/SW walkable, W/NW blocked by
// a wall to the west). All walkable directions stay flat at z=20.
diff --git a/Projects/UOContent.Tests/Tests/Mobiles/AI/ApproachTargetTests.cs b/Projects/UOContent.Tests/Tests/Mobiles/AI/ApproachTargetTests.cs
index fa3eb23bd..923f4aff9 100644
--- a/Projects/UOContent.Tests/Tests/Mobiles/AI/ApproachTargetTests.cs
+++ b/Projects/UOContent.Tests/Tests/Mobiles/AI/ApproachTargetTests.cs
@@ -176,9 +176,10 @@ public class ApproachTargetTests
Assert.True(caught, "chaser must catch a target that walks away then stops");
}
- [Fact]
+ [SkippableFact]
public void UnreachableTarget_GivesUp_AndIdles()
{
+ TileDataRequirement.SkipIfMissing();
var map = Map.Maps[1];
Assert.NotNull(map);
map.GetAverageZ(1500, 1601, out _, out var z, out _);
@@ -243,9 +244,10 @@ public class ApproachTargetTests
Assert.True(stayedIdle, "after giving up, the creature must idle, not shuffle");
}
- [Fact]
+ [SkippableFact]
public void WallBetween_RoutesAround_ReachesTarget()
{
+ TileDataRequirement.SkipIfMissing();
var map = Map.Maps[1];
Assert.NotNull(map);
map.GetAverageZ(1500, 1600, out _, out var z, out _);
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/AosArmorAttributesPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/AosArmorAttributesPropertiesTests.cs
deleted file mode 100644
index 760bec89e..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/AosArmorAttributesPropertiesTests.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.Text;
-using Server;
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class AosArmorAttributesPropertiesTests
-{
- private static Dictionary Decode(ObjectPropertyList opl)
- {
- opl.Terminate();
- var buffer = opl.Buffer;
- var map = new Dictionary();
- var pos = 15;
- while (true)
- {
- var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
- pos += 4;
- if (cliloc == 0)
- {
- break;
- }
-
- var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
- pos += 2;
- map[cliloc] = Encoding.Unicode.GetString(buffer, pos, byteLen);
- pos += byteLen;
- }
-
- return map;
- }
-
- [Fact]
- public void EmitsMageArmorAndSelfRepair_DoesNotReadLowerStatReqOrDurabilityFromContainer()
- {
- var attrs = new AosArmorAttributes(null)
- {
- MageArmor = 1,
- SelfRepair = 4,
- LowerStatReq = 50, // container value must NOT be auto-emitted (it's passed in by the consumer)
- DurabilityBonus = 10 // never emitted by this method
- };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl); // no lowerStatReq arg
- var map = Decode(opl);
-
- Assert.Equal("", map[1060437]); // MageArmor (no-arg)
- Assert.Equal("4", map[1060450]); // SelfRepair
- Assert.False(map.ContainsKey(1060435)); // LowerStatReq NOT read from container
- Assert.False(map.ContainsKey(1060410)); // DurabilityBonus excluded
- }
-
- [Fact]
- public void EmitsLowerStatReqWhenPassed()
- {
- var attrs = new AosArmorAttributes(null) { MageArmor = 1, LowerStatReq = 50 };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl); // computed value passed by the consumer, not the raw 50
- var map = Decode(opl);
-
- Assert.Equal("77", map[1060435]); // emitted from the param, not the container's 50
- Assert.Equal("", map[1060437]); // MageArmor still emitted
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/AosAttributesPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/AosAttributesPropertiesTests.cs
deleted file mode 100644
index 6571c1312..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/AosAttributesPropertiesTests.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.Text;
-using Server;
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class AosAttributesPropertiesTests
-{
- private static Dictionary Decode(ObjectPropertyList opl)
- {
- opl.Terminate();
- var buffer = opl.Buffer;
- var map = new Dictionary();
- var pos = 15;
- while (true)
- {
- var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
- pos += 4;
- if (cliloc == 0)
- {
- break;
- }
-
- var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
- pos += 2;
- map[cliloc] = Encoding.Unicode.GetString(buffer, pos, byteLen);
- pos += byteLen;
- }
-
- return map;
- }
-
- [Fact]
- public void EmitsRawAttributesInCanonicalOrder()
- {
- var attrs = new AosAttributes(null)
- {
- DefendChance = 5,
- BonusStr = 10,
- NightSight = 1,
- SpellChanneling = 1,
- WeaponSpeed = 7
- };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl);
- var map = Decode(opl);
-
- Assert.Equal("5", map[1060408]); // DefendChance
- Assert.Equal("10", map[1060485]); // BonusStr
- Assert.Equal("", map[1060441]); // NightSight (no-arg)
- Assert.Equal("", map[1060482]); // SpellChanneling (no-arg)
- Assert.Equal("7", map[1060486]); // WeaponSpeed
- Assert.False(map.ContainsKey(1060401)); // WeaponDamage not set
- }
-
- [Fact]
- public void AppliesComputedBonuses()
- {
- var attrs = new AosAttributes(null) { WeaponDamage = 10, AttackChance = 4, Luck = 100 };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl, damageBonus: 5, hitChanceBonus: 3, luckBonus: 50);
- var map = Decode(opl);
-
- Assert.Equal("15", map[1060401]); // WeaponDamage + damageBonus
- Assert.Equal("7", map[1060415]); // AttackChance + hitChanceBonus
- Assert.Equal("150", map[1060436]); // Luck + luckBonus
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/AosWeaponAttributesPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/AosWeaponAttributesPropertiesTests.cs
deleted file mode 100644
index f9f3ecdc9..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/AosWeaponAttributesPropertiesTests.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.Text;
-using Server;
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class AosWeaponAttributesPropertiesTests
-{
- private static Dictionary Decode(ObjectPropertyList opl)
- {
- opl.Terminate();
- var buffer = opl.Buffer;
- var map = new Dictionary();
- var pos = 15;
- while (true)
- {
- var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
- pos += 4;
- if (cliloc == 0)
- {
- break;
- }
-
- var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
- pos += 2;
- map[cliloc] = Encoding.Unicode.GetString(buffer, pos, byteLen);
- pos += byteLen;
- }
-
- return map;
- }
-
- [Fact]
- public void EmitsHitEffectsUseBestSkillMageWeaponSelfRepair()
- {
- var attrs = new AosWeaponAttributes(null)
- {
- UseBestSkill = 1,
- HitFireball = 12,
- HitLeechHits = 20,
- MageWeapon = 25,
- SelfRepair = 3
- };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl);
- var map = Decode(opl);
-
- Assert.Equal("", map[1060400]); // UseBestSkill (no-arg)
- Assert.Equal("12", map[1060420]); // HitFireball
- Assert.Equal("20", map[1060422]); // HitLeechHits
- Assert.Equal("5", map[1060438]); // MageWeapon => 30 - 25
- Assert.Equal("3", map[1060450]); // SelfRepair
- Assert.False(map.ContainsKey(1060435)); // LowerStatReq not emitted without the param
- }
-
- [Fact]
- public void EmitsLowerStatReqWhenPassed()
- {
- var attrs = new AosWeaponAttributes(null) { MageWeapon = 25 };
-
- var opl = new ObjectPropertyList(null);
- attrs.GetProperties(opl); // computed value passed by the weapon
- var map = Decode(opl);
-
- Assert.Equal("40", map[1060435]); // lower requirements, in cliloc order before MageWeapon
- Assert.Equal("5", map[1060438]); // MageWeapon still emitted
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/BaseArmorPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/BaseArmorPropertiesTests.cs
deleted file mode 100644
index 5176e6db6..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/BaseArmorPropertiesTests.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class BaseArmorPropertiesTests
-{
- [Fact]
- public void Armor_AttributeLineSet_Preserved()
- {
- var armor = new PlateChest();
- try
- {
- armor.Attributes.DefendChance = 5;
- armor.Attributes.BonusDex = 8;
- armor.Attributes.Luck = 40;
- armor.Attributes.SpellChanneling = 1;
- armor.Attributes.IncreasedKarmaLoss = 2;
- armor.ArmorAttributes.MageArmor = 1;
- armor.ArmorAttributes.SelfRepair = 3;
- armor.ArmorAttributes.LowerStatReq = 50;
-
- var map = ItemOplTestHelper.DecodeAttributeLines(armor);
-
- Assert.Equal("5", map[1060408]); // DefendChance
- Assert.Equal("8", map[1060409]); // BonusDex
- Assert.Equal("40", map[1060436]); // Luck (GetLuckBonus()==0 unequipped)
- Assert.Equal("", map[1060482]); // SpellChanneling
- Assert.Equal("2", map[1075210]); // IncreasedKarmaLoss
- Assert.Equal("", map[1060437]); // MageArmor
- Assert.Equal("3", map[1060450]); // SelfRepair
- Assert.Equal("50", map[1060435]); // LowerStatReq (inline via GetLowerStatReq)
- }
- finally
- {
- armor.Delete();
- }
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/BaseClothingPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/BaseClothingPropertiesTests.cs
deleted file mode 100644
index d7d51c3f3..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/BaseClothingPropertiesTests.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class BaseClothingPropertiesTests
-{
- [Fact]
- public void Clothing_AttributeLineSet_Preserved()
- {
- var shirt = new FancyShirt();
- try
- {
- shirt.Attributes.DefendChance = 5;
- shirt.Attributes.Luck = 25;
- shirt.Attributes.SpellChanneling = 1;
- shirt.ClothingAttributes.MageArmor = 1;
- shirt.ClothingAttributes.SelfRepair = 2;
- shirt.ClothingAttributes.LowerStatReq = 30;
- shirt.ClothingAttributes.DurabilityBonus = 15;
-
- var map = ItemOplTestHelper.DecodeAttributeLines(shirt);
-
- Assert.Equal("5", map[1060408]); // DefendChance
- Assert.Equal("25", map[1060436]); // Luck (raw)
- Assert.Equal("", map[1060482]); // SpellChanneling
- Assert.Equal("", map[1060437]); // MageArmor
- Assert.Equal("2", map[1060450]); // SelfRepair
- Assert.Equal("30", map[1060435]); // LowerStatReq (direct)
- Assert.Equal("15", map[1060410]); // DurabilityBonus (direct)
- }
- finally
- {
- shirt.Delete();
- }
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/BaseJewelPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/BaseJewelPropertiesTests.cs
deleted file mode 100644
index 30acb6cf1..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/BaseJewelPropertiesTests.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class BaseJewelPropertiesTests
-{
- [Fact]
- public void Jewel_AttributeLineSet_Preserved()
- {
- var ring = new GoldRing();
- try
- {
- ring.Attributes.DefendChance = 5;
- ring.Attributes.BonusStr = 10;
- ring.Attributes.Luck = 100;
- ring.Attributes.NightSight = 1;
- ring.Attributes.SpellChanneling = 1;
- ring.Attributes.IncreasedKarmaLoss = 3;
-
- var map = ItemOplTestHelper.DecodeAttributeLines(ring);
-
- Assert.Equal("5", map[1060408]); // DefendChance
- Assert.Equal("10", map[1060485]); // BonusStr
- Assert.Equal("100", map[1060436]); // Luck (raw; jewel has no luck bonus)
- Assert.Equal("", map[1060441]); // NightSight
- Assert.Equal("", map[1060482]); // SpellChanneling
- Assert.Equal("3", map[1075210]); // IncreasedKarmaLoss (Core.ML EJ)
- }
- finally
- {
- ring.Delete();
- }
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/BaseTalismanPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/BaseTalismanPropertiesTests.cs
deleted file mode 100644
index d42684658..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/BaseTalismanPropertiesTests.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class BaseTalismanPropertiesTests
-{
- [Fact]
- public void GetProperties_EmitsAosAttributeLines()
- {
- var item = new RandomTalisman();
- try
- {
- item.Attributes.DefendChance = 10;
- item.Attributes.BonusStr = 5;
- item.Attributes.Luck = 50;
- item.Attributes.NightSight = 1;
- item.Attributes.SpellChanneling = 1;
- item.Attributes.IncreasedKarmaLoss = 2;
-
- var lines = ItemOplTestHelper.DecodeAttributeLines(item);
-
- Assert.True(lines.ContainsKey(1060408)); // DefendChance
- Assert.Equal("10", lines[1060408]);
- Assert.True(lines.ContainsKey(1060485)); // BonusStr
- Assert.Equal("5", lines[1060485]);
- Assert.True(lines.ContainsKey(1060436)); // Luck
- Assert.Equal("50", lines[1060436]);
- Assert.True(lines.ContainsKey(1060441)); // NightSight
- Assert.Equal("", lines[1060441]);
- Assert.True(lines.ContainsKey(1060482)); // SpellChanneling
- Assert.Equal("", lines[1060482]);
- Assert.True(lines.ContainsKey(1075210)); // IncreasedKarmaLoss
- Assert.Equal("2", lines[1075210]);
- }
- finally
- {
- item.Delete();
- }
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/BaseWeaponPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/BaseWeaponPropertiesTests.cs
deleted file mode 100644
index 213e59cb9..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/BaseWeaponPropertiesTests.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class BaseWeaponPropertiesTests
-{
- [Fact]
- public void Weapon_AttributeLineSet_Preserved()
- {
- var weapon = new Longsword();
- try
- {
- weapon.Attributes.DefendChance = 5;
- weapon.Attributes.BonusInt = 6;
- weapon.Attributes.SpellChanneling = 1;
- weapon.WeaponAttributes.UseBestSkill = 1;
- weapon.WeaponAttributes.HitFireball = 12;
- weapon.WeaponAttributes.MageWeapon = 25;
- weapon.WeaponAttributes.SelfRepair = 3;
-
- var map = ItemOplTestHelper.DecodeAttributeLines(weapon);
-
- Assert.Equal("5", map[1060408]); // DefendChance
- Assert.Equal("6", map[1060432]); // BonusInt
- Assert.Equal("", map[1060482]); // SpellChanneling
- Assert.Equal("", map[1060400]); // UseBestSkill
- Assert.Equal("12", map[1060420]); // HitFireball
- Assert.Equal("5", map[1060438]); // MageWeapon (30 - 25)
- Assert.Equal("3", map[1060450]); // SelfRepair
- }
- finally
- {
- weapon.Delete();
- }
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/ItemOplTestHelper.cs b/Projects/UOContent.Tests/Tests/PropertyList/ItemOplTestHelper.cs
deleted file mode 100644
index 1b4acf26b..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/ItemOplTestHelper.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.Text;
-using Server;
-
-namespace UOContent.Tests;
-
-public static class ItemOplTestHelper
-{
- // Builds the item's OPL and returns attribute/property cliloc lines (>= 1060000),
- // ignoring base-item lines (name, weight, etc.) so tests isolate the attribute surface.
- public static Dictionary DecodeAttributeLines(Item item)
- {
- var opl = new ObjectPropertyList(item);
- item.GetProperties(opl);
- opl.Terminate();
-
- var buffer = opl.Buffer;
- var map = new Dictionary();
- var pos = 15;
- while (true)
- {
- var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
- pos += 4;
- if (cliloc == 0)
- {
- break;
- }
-
- var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
- pos += 2;
- var arg = Encoding.Unicode.GetString(buffer, pos, byteLen);
- pos += byteLen;
-
- if (cliloc is >= 1060000 and < 1080000)
- {
- map[cliloc] = arg;
- }
- }
-
- return map;
- }
-}
diff --git a/Projects/UOContent.Tests/Tests/PropertyList/SpellbookPropertiesTests.cs b/Projects/UOContent.Tests/Tests/PropertyList/SpellbookPropertiesTests.cs
deleted file mode 100644
index 4f14d1f71..000000000
--- a/Projects/UOContent.Tests/Tests/PropertyList/SpellbookPropertiesTests.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Server.Items;
-using Xunit;
-
-namespace UOContent.Tests;
-
-[Collection("Sequential UOContent Tests")]
-public class SpellbookPropertiesTests
-{
- [Fact]
- public void GetProperties_EmitsAosAttributeLines()
- {
- var item = new Spellbook();
- try
- {
- item.Attributes.CastRecovery = 3;
- item.Attributes.LowerManaCost = 8;
- item.Attributes.Luck = 75;
- item.Attributes.NightSight = 1;
- item.Attributes.SpellChanneling = 1;
- item.Attributes.IncreasedKarmaLoss = 2;
-
- var lines = ItemOplTestHelper.DecodeAttributeLines(item);
-
- Assert.True(lines.ContainsKey(1060412)); // CastRecovery
- Assert.Equal("3", lines[1060412]);
- Assert.True(lines.ContainsKey(1060433)); // LowerManaCost
- Assert.Equal("8", lines[1060433]);
- Assert.True(lines.ContainsKey(1060436)); // Luck
- Assert.Equal("75", lines[1060436]);
- Assert.True(lines.ContainsKey(1060441)); // NightSight
- Assert.Equal("", lines[1060441]);
- Assert.True(lines.ContainsKey(1060482)); // SpellChanneling
- Assert.Equal("", lines[1060482]);
- Assert.True(lines.ContainsKey(1075210)); // IncreasedKarmaLoss
- Assert.Equal("2", lines[1075210]);
- }
- finally
- {
- item.Delete();
- }
- }
-}
diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs
index 9ac9158c4..6a9ec278c 100644
--- a/Projects/UOContent/Misc/AOS.cs
+++ b/Projects/UOContent/Misc/AOS.cs
@@ -1010,8 +1010,6 @@ namespace Server
return value;
}
- // lowerStatReq is passed in because the weapon folds the resource's lower-requirements into
- // GetLowerStatReq(); emitted in cliloc order (1060435) between the Hit* block and MageWeapon.
public void GetProperties(IPropertyList list)
{
int prop;
@@ -1200,9 +1198,6 @@ namespace Server
return value;
}
- // lowerStatReq is passed in because consumers compute it differently: armor folds in the
- // resource's ArmorLowerRequirements via GetLowerStatReq(), clothing reads it raw. Emitted in
- // cliloc order (1060435) ahead of MageArmor/SelfRepair.
public void GetProperties(IPropertyList list)
{
int prop;