Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
98656cf
Перенос файлов
CattyLucky May 24, 2026
deec75b
Адаптация
CattyLucky May 24, 2026
792d857
change
CattyLucky May 24, 2026
8e91d2c
change
CattyLucky May 25, 2026
9a0ed3b
артефакты
CattyLucky May 25, 2026
de8115d
Темы
CattyLucky May 26, 2026
6136967
Merge branch 'main' into Trades
CattyLucky May 29, 2026
169f07a
Merge branch 'main' into Trades
CattyLucky May 31, 2026
0efd86c
Merge branch 'main' into Trades
CattyLucky Jun 1, 2026
3e44341
Finish
CattyLucky Jun 3, 2026
492f0cc
Finish
CattyLucky Jun 3, 2026
def4234
Organize expedition contract prototypes
CattyLucky Jun 3, 2026
1b42539
Merge branch 'main' into Trades
CattyLucky Jun 3, 2026
c3a58ef
Merge branch 'main' into Trades
CattyLucky Jun 3, 2026
29c695f
исправления на несколько игроков
CattyLucky Jun 8, 2026
52fcc81
исправления ошибки в тестах
CattyLucky Jun 8, 2026
1df58be
исправления ошибки в тестах DeviceNetwork
CattyLucky Jun 8, 2026
5f77455
change
Jun 8, 2026
59d7f02
change
Jun 8, 2026
50e5ee8
Fix hunt contract debris generation
Jun 8, 2026
789d740
Fix hunt dungeon pinpointer and loot fill
Jun 8, 2026
d6e9126
Contracts
Jun 8, 2026
93aa544
Improve contract dungeons and reagent turn-ins
CattyLucky Jun 8, 2026
efa1f7e
Add bank-backed expedition trade currency
CattyLucky Jun 8, 2026
1212009
Add expedition drone hunt contracts
CattyLucky Jun 9, 2026
8d467b4
Merge branch 'main' into Trades
CattyLucky Jun 9, 2026
761c6f8
Make drone hunts require proof core turn-in
CattyLucky Jun 9, 2026
ea6bffe
Fix shipyard CI failures
CattyLucky Jun 9, 2026
4cb7d48
Fix trade logging in integration tests
CattyLucky Jun 9, 2026
0ca7072
Fix hunt dungeon preset bounds
CattyLucky Jun 9, 2026
cb3f3e8
Fix artifact lizard polymorph config
CattyLucky Jun 9, 2026
92431d6
Retry shipyard CI
CattyLucky Jun 9, 2026
fdb5eb8
Add drone hunt proof cleanup and bank debit transactions
CattyLucky Jun 10, 2026
fe084cf
Increase expedition contract rewards and loot
CattyLucky Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions Content.Client/_Forge/Trade/Bound/NcStoreBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
using Content.Shared._Forge.Trade;
using Robust.Client.Player;
using Robust.Client.UserInterface;

namespace Content.Client._Forge.Trade;

public sealed class NcStoreStructuredBoundUi(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
private static readonly TimeSpan CatalogRefreshRetryInterval = TimeSpan.FromSeconds(0.5);
private readonly IPlayerManager _player = IoCManager.Resolve<IPlayerManager>();

private int _lastCatalogRevision = int.MinValue;
private int _lastStateRevision = int.MinValue;

private NcStoreMenu? _menu;

private StoreDynamicState? _pendingDynamic;
private DateTime? _lastCatalogRefreshRequest;

private EntityUid? Actor => _player.LocalSession?.AttachedEntity;

protected override void Open()
{
var wasOpened = IsOpened;
base.Open();

if (wasOpened)
return;

EnsureMenuCreated();
if (_menu == null)
return;

_menu.Visible = false;
RequestUiRefresh();
}

private void DetachMenuHandlers(NcStoreMenu menu)
{
menu.OnBarterPressed -= OnBarter;
menu.OnBuyPressed -= OnBuy;
menu.OnSellPressed -= OnSell;
menu.OnMassSellPulledCrate -= OnMassSellPulledCrate;
menu.OnContractClaim -= OnContractClaim;
menu.OnContractTake -= OnContractTake;
menu.OnContractSkip -= OnContractSkip;
menu.OnContractRequestPinpointer -= OnContractRequestPinpointer;
menu.OnVisibleListingIdsChanged -= OnVisibleListingIdsChanged;
menu.OnClose -= OnMenuClosed;
}

protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);

switch (message)
{
case StoreCatalogMessage cat:
ReceiveCatalog(cat);
break;
case StoreDynamicState st:
ReceiveDynamic(st);
break;
}
}

private void ReceiveDynamic(StoreDynamicState st)
{
EnsureMenuCreated();
if (_menu == null)
return;

if (st.CatalogRevision != _lastCatalogRevision)
{
_pendingDynamic = st;
_menu.Visible = false;
RequestUiRefreshIfDue();

return;
}

if (st.Revision <= _lastStateRevision)
return;

_lastStateRevision = st.Revision;

_lastCatalogRefreshRequest = null;

ApplyDynamic(st);
_menu.Visible = true;
}

private void ReceiveCatalog(StoreCatalogMessage cat)
{
EnsureMenuCreated();
if (_menu == null)
return;

if (cat.CatalogRevision == _lastCatalogRevision)
return;

_lastCatalogRevision = cat.CatalogRevision;
_lastStateRevision = int.MinValue;

_menu.PopulateCatalog(
cat.Listings,
cat.HasBuyTab,
cat.HasSellTab,
cat.HasBarterTab,
cat.HasContractsTab,
cat.UiColors);

if (_pendingDynamic is { } pending &&
pending.CatalogRevision == _lastCatalogRevision)
{
_pendingDynamic = null;
_lastCatalogRefreshRequest = null;
_lastStateRevision = pending.Revision;
ApplyDynamic(pending);
_menu.Visible = true;
}
else
_menu.Visible = false;
}

private void ApplyDynamic(StoreDynamicState st) =>
_menu!.ApplyDynamicState(
st.BalanceByCurrency,
st.RemainingById,
st.OwnedById,
st.CrateUnitsById,
st.MassSellTotals,
st.HasBuyTab,
st.HasSellTab,
st.HasBarterTab,
st.HasContractsTab,
st.Contracts,
st.ContractSkipCost,
st.ContractSkipCurrency,
st.IsSparseDynamicSnapshot,
st.SnapshotScopeIds);

private void EnsureMenuCreated()
{
if (_menu != null)
return;

_menu = this.CreateWindow<NcStoreMenu>();
_menu.Visible = false;

_lastCatalogRevision = int.MinValue;
_lastStateRevision = int.MinValue;
_pendingDynamic = null;
_lastCatalogRefreshRequest = null;

if (EntMan.TryGetComponent(Owner, out MetaDataComponent? meta))
_menu.SetDisplayTitle(meta.EntityName);
else
_menu.SetDisplayTitle(string.Empty);

_menu.OnBarterPressed += OnBarter;
_menu.OnBuyPressed += OnBuy;
_menu.OnSellPressed += OnSell;
_menu.OnMassSellPulledCrate += OnMassSellPulledCrate;
_menu.OnContractClaim += OnContractClaim;
_menu.OnContractTake += OnContractTake;
_menu.OnContractSkip += OnContractSkip;
_menu.OnContractRequestPinpointer += OnContractRequestPinpointer;
_menu.OnVisibleListingIdsChanged += OnVisibleListingIdsChanged;

_menu.OnClose += OnMenuClosed;
}

private void OnMenuClosed()
{
if (_menu == null)
return;

DetachMenuHandlers(_menu);

_menu.CleanupBeforeClose();
_menu.Orphan();
_menu = null;
_lastCatalogRevision = int.MinValue;
_lastStateRevision = int.MinValue;
_pendingDynamic = null;
_lastCatalogRefreshRequest = null;
}

private void OnBuy(StoreListingData data, int qty)
{
if (Actor == null)
return;

SendMessage(new StoreBuyListingBoundUiMessage(data.ListingId, qty));
}

private void OnSell(StoreListingData data, int qty)
{
if (Actor == null)
return;

SendMessage(new StoreSellListingBoundUiMessage(data.ListingId, qty, data.Flavor == StoreListingFlavor.Crate));
}

private void OnBarter(StoreListingData data, int qty)
{
if (Actor == null)
return;

SendMessage(new StoreBarterListingBoundUiMessage(data.ListingId, qty));
}

private void OnContractClaim(string contractId)
{
if (Actor == null)
return;

SendMessage(new ClaimContractBoundMessage(contractId));
}

private void OnContractTake(string contractId)
{
if (Actor == null)
return;

SendMessage(new TakeContractBoundMessage(contractId));
}

private void OnContractSkip(string contractId)
{
if (Actor == null)
return;

SendMessage(new SkipContractBoundMessage(contractId));
}

private void OnContractRequestPinpointer(string contractId)
{
if (Actor == null)
return;

SendMessage(new RequestContractPinpointerBoundMessage(contractId));
}

private void OnMassSellPulledCrate()
{
if (Actor == null)
return;

SendMessage(new StoreMassSellPulledCrateBoundUiMessage());
}

private void OnVisibleListingIdsChanged(string[] ids)
{
if (Actor == null)
return;

SendMessage(new StoreSetVisibleListingsBoundUiMessage(ids));
}

protected override void Dispose(bool disposing)
{
if (_menu != null)
{
DetachMenuHandlers(_menu);
_menu.CleanupBeforeClose();
_menu.Orphan();
_menu = null;
}

_lastCatalogRevision = int.MinValue;
_lastStateRevision = int.MinValue;
_pendingDynamic = null;
_lastCatalogRefreshRequest = null;

base.Dispose(disposing);
}

private void RequestUiRefreshIfDue()
{
var now = DateTime.UtcNow;
if (_lastCatalogRefreshRequest is { } last &&
now - last < CatalogRefreshRetryInterval)
return;

RequestUiRefresh(now);
}

private void RequestUiRefresh(DateTime? now = null)
{
if (Actor == null)
return;

_lastCatalogRefreshRequest = now ?? DateTime.UtcNow;
SendMessage(new RequestUiRefreshMessage());
}
}
90 changes: 90 additions & 0 deletions Content.Client/_Forge/Trade/Controls/NcBarterPreviewControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;


namespace Content.Client._Forge.Trade.Controls;


[GenerateTypedNameReferences]
public sealed partial class NcBarterPreviewControl : PanelContainer
{
private static readonly Color FrameBackground = Color.FromHex("#15181D");
private static readonly Color FrameBorder = Color.FromHex("#3D3420");
private static readonly Color TextColor = Color.FromHex("#D7D0C1");
private static readonly Color ArrowColor = Color.FromHex("#B98B3A");

public NcBarterPreviewControl()
{
RobustXamlLoader.Load(this);
ApplyFrame(CostFrame);
ApplyFrame(ReceiveFrame);
CostLabel.FontColorOverride = TextColor;
ReceiveLabel.FontColorOverride = TextColor;
ArrowLabel.ModulateSelfOverride = ArrowColor;
}

public void Clear()
{
Visible = false;
CostLabel.Text = string.Empty;
CostLabel.ToolTip = null;
ReceiveLabel.Text = string.Empty;
ReceiveLabel.ToolTip = null;
CostIcon.Visible = false;
ReceiveIcon.Visible = false;
}

public void SetPreview(
string costText,
string receiveText,
string? costIconPrototype,
string? receiveIconPrototype
)
{
Visible = true;

CostLabel.Text = string.IsNullOrWhiteSpace(costText)
? Loc.GetString("nc-store-barter-cost-empty")
: costText;
CostLabel.ToolTip = CostLabel.Text;

ReceiveLabel.Text = string.IsNullOrWhiteSpace(receiveText)
? Loc.GetString("nc-store-barter-receive-empty")
: receiveText;
ReceiveLabel.ToolTip = ReceiveLabel.Text;

SetPrototypeIcon(CostIcon, costIconPrototype);
SetPrototypeIcon(ReceiveIcon, receiveIconPrototype);
}

private static void SetPrototypeIcon(EntityPrototypeView view, string? prototype)
{
if (string.IsNullOrWhiteSpace(prototype))
{
view.Visible = false;
return;
}

view.Visible = true;
view.SetPrototype(prototype);
}

private static void ApplyFrame(PanelContainer frame)
{
var style = new StyleBoxFlat
{
BackgroundColor = FrameBackground,
BorderColor = FrameBorder,
BorderThickness = new(1)
};

style.SetContentMarginOverride(StyleBox.Margin.Left, 0);
style.SetContentMarginOverride(StyleBox.Margin.Top, 0);
style.SetContentMarginOverride(StyleBox.Margin.Right, 0);
style.SetContentMarginOverride(StyleBox.Margin.Bottom, 0);

frame.PanelOverride = style;
}
}
Loading
Loading