Skip to content

[Feature] Antag purchase list - #1184

Open
DEADISKO wants to merge 3 commits into
WWhiteDreamProject:masterfrom
DEADISKO:feature/antag-purchase-list
Open

[Feature] Antag purchase list#1184
DEADISKO wants to merge 3 commits into
WWhiteDreamProject:masterfrom
DEADISKO:feature/antag-purchase-list

Conversation

@DEADISKO

@DEADISKO DEADISKO commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Описание PR

В конце раунда в манифесте у антагонистов отображаются покупки и общая потраченная сумма валюты. Поддерживает скидки и возвраты. Группирует покупки по листингу и уплаченной цене.


Медиа

Список

2026-8-06_00 00 47 2026-8-06_00 00 52 2026-8-06_00 00 41


Изменения

🆑 DEADISKO

  • add: Список покупок антагониста в конце раунда

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Добавлена история покупок антагонистов. Система сохраняет стоимость покупок, обрабатывает возвраты и добавляет данные в итоговый текст целей. Клиент отображает товары, иконки и tooltip. Интеграционный тест проверяет серверный и клиентский сценарии.

Changes

История покупок и контракты

Layer / File(s) Summary
Контракты и сериализация покупок
Content.Shared/_White/AntagPurchaseHistory/*, Content.Shared/Store/StoreBuyFinishedEvent.cs
Добавлены компонент истории, модель записи, событие возврата и сериализация стоимости в валюте.
Запись покупок и обработка возвратов
Content.Server/_White/AntagPurchaseHistory/AntagPurchaseHistorySystem.cs, Content.Server/Store/Systems/StoreSystem.Ui.cs
Система записывает покупки антагонистов, сохраняет исходную и итоговую стоимость и помечает покупки возвращёнными после успешного возврата.
Добавление истории в цели
Content.Server/Objectives/ObjectivesSystem.cs, Resources/Locale/*/_white/objectives/antag-purchase-history.ftl
ObjectivesSystem получает дополнительные строки и включает в итоговую сводку markup невозвращённых покупок с локализованной суммой.
Клиентское отображение покупок
Content.Client/_White/AntagPurchaseHistory/AntagPurchaseTag.cs
AntagPurchaseTag проверяет атрибуты markup, выбирает иконку listing и создаёт tooltip с итоговой стоимостью и скидкой.
Интеграционная проверка истории
Content.IntegrationTests/Tests/_White/AntagPurchaseHistoryTests.cs
Тест проверяет снимки стоимости, round-end markup, возвраты, клиентские иконки и tooltip, а также отсутствие истории у неантагонистов.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: remuchi, deathb4defeat, sleepyyapril

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Заголовок точно описывает основное изменение: добавление списка покупок антагониста.
Description check ✅ Passed Описание связано с изменениями и указывает отображение покупок, скидок, возвратов и общей суммы в конце раунда.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.Client/_White/AntagPurchaseHistory/AntagPurchaseTag.cs`:
- Around line 142-151: Stabilize the currency order in the tooltip by ordering
the dictionary entries by pair.Key.Id before the Select in the AntagPurchaseTag
price formatting expression. Preserve the existing currency lookup and localized
display behavior after sorting.

In `@Content.Server/_White/AntagPurchaseHistory/AntagPurchaseHistorySystem.cs`:
- Around line 94-137: Добавьте сериализованный OriginalCost в ключ группировки
groupedPurchases рядом с ListingId и FinalCost, чтобы покупки с разной исходной
ценой попадали в разные группы. В цикле формирования markup используйте значение
OriginalCost из ключа группы вместо purchase.OriginalCost из group.First(),
сохранив остальные атрибуты без изменений.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3be8fd0e-ddff-42dd-8a22-0fdc900f4a24

📥 Commits

Reviewing files that changed from the base of the PR and between 68a2acb and 14565f5.

📒 Files selected for processing (10)
  • Content.Client/_White/AntagPurchaseHistory/AntagPurchaseTag.cs
  • Content.IntegrationTests/Tests/_White/AntagPurchaseHistoryTests.cs
  • Content.Server/Objectives/ObjectivesSystem.cs
  • Content.Server/Store/Systems/StoreSystem.Ui.cs
  • Content.Server/_White/AntagPurchaseHistory/AntagPurchaseHistorySystem.cs
  • Content.Shared/Store/StoreBuyFinishedEvent.cs
  • Content.Shared/_White/AntagPurchaseHistory/AntagPurchaseHistoryComponent.cs
  • Content.Shared/_White/AntagPurchaseHistory/AntagPurchaseMarkup.cs
  • Resources/Locale/en-US/_white/objectives/antag-purchase-history.ftl
  • Resources/Locale/ru-RU/_white/objectives/antag-purchase-history.ftl

Comment on lines +142 to +151
return string.Join(", ", cost.Select(pair =>
{
if (!_prototypes.TryIndex(pair.Key, out CurrencyPrototype? currency))
return $"{pair.Value} {pair.Key.Id}";

return Loc.GetString(
"store-ui-price-display",
("amount", pair.Value),
("currency", Loc.GetString(currency.DisplayName, ("amount", pair.Value))));
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Стабилизируйте порядок валют в tooltip.

Серверный GetPriceString в Content.Server/_White/AntagPurchaseHistory/AntagPurchaseHistorySystem.cs сортирует валюты по Id. Клиентский код перечисляет Dictionary без сортировки. Поэтому порядок цен в tooltip может не совпасть с итоговым текстом раунда для одной покупки с несколькими валютами.

Добавьте OrderBy(pair => pair.Key.Id) перед Select.

Предлагаемое исправление
-        return string.Join(", ", cost.Select(pair =>
+        return string.Join(", ", cost
+            .OrderBy(pair => pair.Key.Id)
+            .Select(pair =>
         {
             if (!_prototypes.TryIndex(pair.Key, out CurrencyPrototype? currency))
                 return $"{pair.Value} {pair.Key.Id}";
 
             return Loc.GetString(
                 "store-ui-price-display",
                 ("amount", pair.Value),
                 ("currency", Loc.GetString(currency.DisplayName, ("amount", pair.Value))));
-        }));
+        }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return string.Join(", ", cost.Select(pair =>
{
if (!_prototypes.TryIndex(pair.Key, out CurrencyPrototype? currency))
return $"{pair.Value} {pair.Key.Id}";
return Loc.GetString(
"store-ui-price-display",
("amount", pair.Value),
("currency", Loc.GetString(currency.DisplayName, ("amount", pair.Value))));
}));
return string.Join(", ", cost
.OrderBy(pair => pair.Key.Id)
.Select(pair =>
{
if (!_prototypes.TryIndex(pair.Key, out CurrencyPrototype? currency))
return $"{pair.Value} {pair.Key.Id}";
return Loc.GetString(
"store-ui-price-display",
("amount", pair.Value),
("currency", Loc.GetString(currency.DisplayName, ("amount", pair.Value))));
}));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/_White/AntagPurchaseHistory/AntagPurchaseTag.cs` around lines
142 - 151, Stabilize the currency order in the tooltip by ordering the
dictionary entries by pair.Key.Id before the Select in the AntagPurchaseTag
price formatting expression. Preserve the existing currency lookup and localized
display behavior after sorting.

Comment on lines +94 to +137
var groupedPurchases = purchases
.GroupBy(purchase => (
purchase.ListingId,
FinalCost: AntagPurchaseMarkup.SerializeCost(purchase.FinalCost)))
.ToList();

var totalCost = new Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2>();
foreach (var purchase in purchases)
{
foreach (var (currency, amount) in purchase.FinalCost)
totalCost[currency] = totalCost.GetValueOrDefault(currency) + amount;
}

var message = new FormattedMessage();
message.AddText(Loc.GetString(
"antag-purchase-history-used",
("amounts", GetPriceString(totalCost))));
message.AddText(" ");

for (var i = 0; i < groupedPurchases.Count; i++)
{
var group = groupedPurchases[i];
var purchase = group.First();

if (i > 0)
message.AddText(", ");

// The opening bracket must be escaped because this FormattedMessage is converted back to markup
// before it is parsed by the client.
message.AddText(FormattedMessage.EscapeText("["));
if (group.Count() > 1)
message.AddText($"{group.Count()}x ");

var attributes = new Dictionary<string, MarkupParameter>
{
[AntagPurchaseMarkup.FinalCostAttribute] = new(group.Key.FinalCost),
[AntagPurchaseMarkup.OriginalCostAttribute] = new(
AntagPurchaseMarkup.SerializeCost(purchase.OriginalCost)),
};
message.PushTag(
new MarkupNode(
AntagPurchaseMarkup.TagName,
new MarkupParameter(purchase.ListingId.Id),
attributes),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Добавьте OriginalCost в ключ группировки.

Группа содержит записи с одинаковыми ListingId и FinalCost. Строки 130-131 передают OriginalCost только из group.First().

Если исходная цена или скидка изменилась между покупками, tooltip покажет исходную цену первой записи для всей группы. Это даёт неверную информацию о скидке. Сериализуйте OriginalCost в ключе группы и используйте это значение в атрибуте markup.

Предлагаемое исправление
         var groupedPurchases = purchases
             .GroupBy(purchase => (
                 purchase.ListingId,
-                FinalCost: AntagPurchaseMarkup.SerializeCost(purchase.FinalCost)))
+                FinalCost: AntagPurchaseMarkup.SerializeCost(purchase.FinalCost),
+                OriginalCost: AntagPurchaseMarkup.SerializeCost(purchase.OriginalCost)))
             .ToList();
@@
                 [AntagPurchaseMarkup.FinalCostAttribute] = new(group.Key.FinalCost),
-                [AntagPurchaseMarkup.OriginalCostAttribute] = new(
-                    AntagPurchaseMarkup.SerializeCost(purchase.OriginalCost)),
+                [AntagPurchaseMarkup.OriginalCostAttribute] = new(group.Key.OriginalCost),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var groupedPurchases = purchases
.GroupBy(purchase => (
purchase.ListingId,
FinalCost: AntagPurchaseMarkup.SerializeCost(purchase.FinalCost)))
.ToList();
var totalCost = new Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2>();
foreach (var purchase in purchases)
{
foreach (var (currency, amount) in purchase.FinalCost)
totalCost[currency] = totalCost.GetValueOrDefault(currency) + amount;
}
var message = new FormattedMessage();
message.AddText(Loc.GetString(
"antag-purchase-history-used",
("amounts", GetPriceString(totalCost))));
message.AddText(" ");
for (var i = 0; i < groupedPurchases.Count; i++)
{
var group = groupedPurchases[i];
var purchase = group.First();
if (i > 0)
message.AddText(", ");
// The opening bracket must be escaped because this FormattedMessage is converted back to markup
// before it is parsed by the client.
message.AddText(FormattedMessage.EscapeText("["));
if (group.Count() > 1)
message.AddText($"{group.Count()}x ");
var attributes = new Dictionary<string, MarkupParameter>
{
[AntagPurchaseMarkup.FinalCostAttribute] = new(group.Key.FinalCost),
[AntagPurchaseMarkup.OriginalCostAttribute] = new(
AntagPurchaseMarkup.SerializeCost(purchase.OriginalCost)),
};
message.PushTag(
new MarkupNode(
AntagPurchaseMarkup.TagName,
new MarkupParameter(purchase.ListingId.Id),
attributes),
var groupedPurchases = purchases
.GroupBy(purchase => (
purchase.ListingId,
FinalCost: AntagPurchaseMarkup.SerializeCost(purchase.FinalCost),
OriginalCost: AntagPurchaseMarkup.SerializeCost(purchase.OriginalCost)))
.ToList();
var totalCost = new Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2>();
foreach (var purchase in purchases)
{
foreach (var (currency, amount) in purchase.FinalCost)
totalCost[currency] = totalCost.GetValueOrDefault(currency) + amount;
}
var message = new FormattedMessage();
message.AddText(Loc.GetString(
"antag-purchase-history-used",
("amounts", GetPriceString(totalCost))));
message.AddText(" ");
for (var i = 0; i < groupedPurchases.Count; i++)
{
var group = groupedPurchases[i];
var purchase = group.First();
if (i > 0)
message.AddText(", ");
// The opening bracket must be escaped because this FormattedMessage is converted back to markup
// before it is parsed by the client.
message.AddText(FormattedMessage.EscapeText("["));
if (group.Count() > 1)
message.AddText($"{group.Count()}x ");
var attributes = new Dictionary<string, MarkupParameter>
{
[AntagPurchaseMarkup.FinalCostAttribute] = new(group.Key.FinalCost),
[AntagPurchaseMarkup.OriginalCostAttribute] = new(group.Key.OriginalCost),
};
message.PushTag(
new MarkupNode(
AntagPurchaseMarkup.TagName,
new MarkupParameter(purchase.ListingId.Id),
attributes),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/_White/AntagPurchaseHistory/AntagPurchaseHistorySystem.cs`
around lines 94 - 137, Добавьте сериализованный OriginalCost в ключ группировки
groupedPurchases рядом с ListingId и FinalCost, чтобы покупки с разной исходной
ценой попадали в разные группы. В цикле формирования markup используйте значение
OriginalCost из ключа группы вместо purchase.OriginalCost из group.First(),
сохранив остальные атрибуты без изменений.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant