ModernUO/Projects/UOContent.Tests
Kamron Batman c00b907c49
fix(housing): register doors, and stop crashing on client component sheets
Players could not place any door while customizing a house, and placing other
pieces could disconnect them outright. Staff saw neither problem, because
Designer_Build only enforces ValidPiece below GameMaster.

Doors: the retail client's doors.txt separates its header rows with lines of
bare tabs. Spreadsheet.ReadLine skipped lines only when `line.Length > 0`, so a
10-tab line was accepted as the names row - every column ended up named "",
GetColumnID resolved nothing, and not one of the 230 door graphics was
registered. Unregistered pieces keep the NotAComponent sentinel, so ValidPiece
rejected every door. The client skips these lines with IsNullOrWhiteSpace; now
so do we. Verified against a retail 7.0.x doors.txt: 0 door graphics before,
230 after.

Crash: every sheet ends in a cosmetic Comment column that is never read, and
client sheets write an empty comment as a plain newline with no trailing tab.
Split('\t') then returns one field fewer than the header declares and the
parser indexed past the end, throwing IndexOutOfRangeException out of the
design packet handler. The client's parser only requires the columns up to
FeatureMask (ClassicUO's CustomHouseMisc.Parse guards on `scanf.Length >= 12`
for a 13-column misc.txt), so such a row is valid data listing real pieces.
Treat a missing trailing field as empty rather than dropping the row, which
would unregister every piece the row lists.

EnsureLoaded also set _loaded before loading, so after the throw an all -1
table stayed cached and rejected everything for players from then on - the same
symptom as #2500.

Also made the failure modes explicit rather than accidental:

- Named the table sentinels. NotAComponent (-1) is the anti-cheat guard and the
  initial state; NoFeatureRequired (0) is a piece with no expansion gate.
- A sheet with no FeatureMask column is now refused and logged. GetInt32 on a
  missing column returns NoFeatureRequired, which would have silently marked
  every piece in that sheet unconditionally placeable regardless of expansion.
- A sheet matching none of its expected tile columns is refused and logged.
  Individual missing columns stay tolerated: older sheets predate columns such
  as walls.txt's SecondAltWindowS/E.
- Catch per sheet, so one unreadable file no longer costs the other six.
- Guard the header rows: an empty file or a types-only file raised a
  NullReferenceException, and a short names row indexed past the end.
- Fall back to the component sheets when housing.bin cannot be read, rather
  than passing null into a SpanReader.

_loaded is still set before loading, deliberately: retrying would re-read every
sheet on each subsequent placement attempt from the packet path.

Sheet precedence is unchanged - the client's copies stay authoritative and
Data/Components remains the fallback.

Co-authored-by: SynPDX <mickelonis@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 19:55:27 -07:00
..
Fixtures feat(network): pluggable connection filters; file blocklist + contribute-first CrowdSec (#2542) 2026-07-25 11:59:37 -07:00
Tests fix(housing): register doors, and stop crashing on client component sheets 2026-08-04 19:55:27 -07:00
UOContent.Tests.csproj fix: Bumps dependencies. (#2531) 2026-07-14 15:17:55 -07:00