From 7f700d3c781619821b7ec93faa400ba7165b39f6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 8 Oct 2026 14:34:47 +0200 Subject: [PATCH] Items are titled by their product: ItemManager.Title, read by every title site Title derives the brand's product name and falls back to the design name. The trade list, buy dialog and properties title read it; the product name line leaves the properties body. Build not yet verified at this commit. Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/ItemManager.cs | 5 ++ Assets/Scripts/UI/Menu/TradeMenu.cs | 8 +- .../UI/Properties Panel/PropertiesPanel.cs | 4 +- tests/Aetheria.Shared.Tests/ItemTitleTests.cs | 75 +++++++++++++++++++ 4 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 tests/Aetheria.Shared.Tests/ItemTitleTests.cs diff --git a/Assets/Scripts/ServerShared/ItemManager.cs b/Assets/Scripts/ServerShared/ItemManager.cs index 281228165..bebfb5431 100644 --- a/Assets/Scripts/ServerShared/ItemManager.cs +++ b/Assets/Scripts/ServerShared/ItemManager.cs @@ -221,6 +221,11 @@ public class ItemManager return (ItemData.Get(maker), product); } + // What the player reads an item as: its product's name when its lot's maker has a product of this design, + // otherwise the design's own name (unbranded, console-given and extracted lots, and commodities). + public string Title(ItemInstance item) => + (item is CraftedItemInstance crafted ? Brand(crafted).Product?.Name : null) ?? GetData(item).Name; + public (RarityTier tier, int upgrades) GetTier(CraftedItemInstance item) { var quality = GetLot(item).Quality; diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index 5c3245907..1adadcf15 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -192,9 +192,9 @@ public class TradeMenu : MonoBehaviour columns.Add(("Name", 3, x => () => x.item is CraftedItemInstance craftedItemInstance ? - $"{x.data.Name}" : - x.data.Name, - x => x.data.Name)); + $"{GameManager.ItemManager.Title(x.item)}" : + GameManager.ItemManager.Title(x.item), + x => GameManager.ItemManager.Title(x.item))); if(_hardpointFilter.filter==null) columns.Add(("Type", 2, x => () => @@ -352,7 +352,7 @@ public class TradeMenu : MonoBehaviour { int quantity = 1; Dialog.Clear(); - Dialog.Title.text = $"Buying {i.data.Name}"; + Dialog.Title.text = $"Buying {GameManager.ItemManager.Title(i.item)}"; Dialog.AddField("Quantity", () => quantity, q => quantity = min(min(q, GameManager.Credits / StationServices.BuyPrice(GameManager.ItemManager, s)), s.Quantity)); diff --git a/Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs b/Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs index 3e8a3c5ae..a39226ccc 100644 --- a/Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs +++ b/Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs @@ -376,7 +376,6 @@ public class PropertiesPanel : MonoBehaviour sheet.AddStat("Manufacturer", () => maker.Name); if (product != null) { - AddProperty(product.Name); AddProperty(product.Description); } } @@ -430,10 +429,9 @@ public class PropertiesPanel : MonoBehaviour private string GetTitle(EquippableItem item) { - var data = GameManager.ItemManager.GetData(item); var (tier, upgrades) = GameManager.ItemManager.GetTier(item); return - $"{data.Name} ({tier.Name}{new string('+', upgrades)})"; + $"{GameManager.ItemManager.Title(item)} ({tier.Name}{new string('+', upgrades)})"; } public void Inspect(EquippedItem item) diff --git a/tests/Aetheria.Shared.Tests/ItemTitleTests.cs b/tests/Aetheria.Shared.Tests/ItemTitleTests.cs new file mode 100644 index 000000000..04423732d --- /dev/null +++ b/tests/Aetheria.Shared.Tests/ItemTitleTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using CultMath; +using GameCult.Caching; +using Xunit; + +// Items are titled by their product (ruling catalog-grows-generic-designs-branded-products): ItemManager.Title reads +// Brand, so the lot's maker picks which of a design's products names the unit, and nothing else carries a name. +public sealed class ItemTitleTests : IDisposable +{ + private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-itemtitle-" + Guid.NewGuid().ToString("N")); + private readonly CultCache _cache; + private readonly ItemManager _items; + private readonly FactionProductData _panopticon, _clapBack; + private readonly GearData _array; + + public ItemTitleTests() + { + Directory.CreateDirectory(_root); + _cache = AetheriaStores.Open(Path.Combine(_root, "Aetheria.cc"), catalogWritable: true); + _cache.Upsert(new TestCatalogGlobal { Name = "Temperament" }); + _array = new GearData { Name = "Array", Hardpoint = HardpointType.Sensors, Shape = new Shape(), Price = 10, Durability = 5 }; + _cache.Upsert(_array); + _cache.Upsert(new SimpleCommodityData { Name = "Ore", Shape = new Shape(), Price = 7, MaxStack = 100 }); + var finch = _cache.Upsert(new Faction { Name = "Finch", ShortName = "FIN" }); + var lucent = _cache.Upsert(new Faction { Name = "Lucent", ShortName = "LUC" }); + var design = _cache.RefOf(_cache.GetByName("Array")); + _panopticon = new FactionProductData { Name = "Panopticon Prime", Design = design, Manufacturer = finch }; + _clapBack = new FactionProductData { Name = "ClapBack Ultra", Design = design, Manufacturer = lucent }; + _cache.Commit(batch => batch.Upsert(typeof(FactionProductData), _panopticon, new CultRecordKey("array-finch"))); + _cache.Commit(batch => batch.Upsert(typeof(FactionProductData), _clapBack, new CultRecordKey("array-lucent"))); + _items = new ItemManager(_cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { }); + } + + public void Dispose() + { + _cache.Dispose(); + Directory.Delete(_root, true); + } + + [Fact] + public void A_product_unit_is_titled_by_its_product() + { + var unit = _items.CreateInstance(_items.CreateLot(_panopticon)); + Assert.Equal("Panopticon Prime", _items.Title(unit)); + Assert.NotEqual("Panopticon Prime", _items.GetData(unit).Name); + } + + [Fact] + public void Two_makers_title_their_own_units() + { + var finch = _items.CreateInstance(_items.CreateLot(_panopticon)); + var lucent = _items.CreateInstance(_items.CreateLot(_clapBack)); + Assert.Equal(_items.GetData(finch).Name, _items.GetData(lucent).Name); + Assert.Equal("Panopticon Prime", _items.Title(finch)); + Assert.Equal("ClapBack Ultra", _items.Title(lucent)); + } + + [Fact] + public void Unbranded_units_are_titled_by_design() + { + var given = _items.CreateInstance(_items.CreateLot(_array, default, .5f)); + Assert.Equal("Array", _items.Title(given)); + + var extractedLot = _items.Lots.Add(new Lot + { + Design = _cache.RefOf(_array), Origin = new Extracted(), Quality = .5f, Roles = new List() + }); + Assert.Equal("Array", _items.Title(_items.CreateInstance(extractedLot))); + + var ore = new SimpleCommodity { Data = _cache.RefOf(_cache.GetByName("Ore")), Quantity = 3 }; + Assert.Equal("Ore", _items.Title(ore)); + } +}