Reports
Report-a-player: rate-capped, recorded, surfaced via an admin decorator.
Providers you can swap 2
Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.
IReportClock provider
Wall-clock seam (unix UTC ms) so rate-cap math is testable. Piece-local on purpose.
-
long UtcNowMs { get; }
IReportStore provider
Persistence seam for filed reports. The default InMemoryReportStore is single-node and volatile; register a document-backed implementation BEFORE AddCrossplayReports (e.g. via AddCrossplayPersistentStores) to survive restarts / share across a cluster. Genre-neutral: it stores opaque ReportRecord rows and never interprets the category/details.
-
int CountByReporterSince(long reporterAccountId, long sinceUtcMs)How many reports reporterAccountId filed at or after sinceUtcMs (unix UTC ms) — backs the per-hour rate cap.
-
long NextId()Allocates the next unique report id.
-
IReadOnlyList<ReportRecord> Recent(int count)The newest reports first, up to count — the operators' review feed.
-
void Save(ReportRecord record)Persists a filed report.
Services you call 1
Crossplay implements these. Resolve them from DI and call them from your own systems.
IReportService
The report-a-player pipeline: validate the target + details, enforce the per-hour rate cap, then record. Genre-neutral — the category byte and details string are the game's vocabulary. Inject and call from the game's own systems; the built-in ReportHandlers already wires the report wire message to it.
-
IReadOnlyList<ReportRecord> Recent(int count)The freshest reports (for the operators' review surface).
-
bool TryReport(ISession session, string targetName, byte category, string details, out ushort reason)Files a report by session against the player named targetName: checks the details length, resolves the target online, applies the rate cap, then records it.
Configuration 1
Every tunable lives in an options object — there are no magic numbers to hunt for.
ReportsOptions
Reports tuning (defaults here, never inline).
-
int AdminPageSize { get; set; }How many recent reports the admin surface returns.
-
int MaxDetailsLength { get; set; }Longest allowed details text.
-
int MaxReportsPerHour { get; set; }Reports one account may file per hour (0 = uncapped).
Wire messages 2
The protocol this piece speaks. Ids are allocated per piece so they can never collide.
ReportRequest Client → server: report a player. The category byte and details string are the GAME's vocabulary — the framework only records them for the operators' review surface.
ReportResponse Server → client: the report verdict.
Unity services you inject 1
Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.
IReportClient
The Reports piece, client side: report a player. The category byte and details string are the GAME's vocabulary — this client never interprets them.
-
UniTask<ReportResponse> ReportAsync(string targetName, byte category, string details, CancellationToken ct = default)Files a report (refusals carry the reason code).
UI views you can replace 1
Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.
ICrossplayReportsView
Narrow contract the presenter drives. UI-only: render calls in, user intents out.