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 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-08 14:34:47 +02:00
commit 7f700d3c78
4 changed files with 85 additions and 7 deletions

View file

@ -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;

View file

@ -192,9 +192,9 @@ public class TradeMenu : MonoBehaviour
columns.Add(("Name", 3,
x => () => x.item is CraftedItemInstance craftedItemInstance ?
$"<color=#{ColorUtility.ToHtmlStringRGB(GameManager.ItemManager.GetTier(craftedItemInstance).tier.Color.ToColor())}>{x.data.Name}" :
x.data.Name,
x => x.data.Name));
$"<color=#{ColorUtility.ToHtmlStringRGB(GameManager.ItemManager.GetTier(craftedItemInstance).tier.Color.ToColor())}>{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));

View file

@ -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
$"<color=#{ColorUtility.ToHtmlStringRGB(tier.Color.ToColor())}>{data.Name}</color><smallcaps><size=60%> ({tier.Name}{new string('+', upgrades)})";
$"<color=#{ColorUtility.ToHtmlStringRGB(tier.Color.ToColor())}>{GameManager.ItemManager.Title(item)}</color><smallcaps><size=60%> ({tier.Name}{new string('+', upgrades)})";
}
public void Inspect(EquippedItem item)

View file

@ -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<CraftedItemData>(_cache.GetByName<GearData>("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<ItemData>(_array), Origin = new Extracted(), Quality = .5f, Roles = new List<RoleFill>()
});
Assert.Equal("Array", _items.Title(_items.CreateInstance(extractedLot)));
var ore = new SimpleCommodity { Data = _cache.RefOf<ItemData>(_cache.GetByName<SimpleCommodityData>("Ore")), Quantity = 3 };
Assert.Equal("Ore", _items.Title(ore));
}
}