mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-26 07:28:45 -07:00
6ee1d6f9b3
Every change below was checked against the 2023-03-21 client disassembly;
file:line evidence is in the code comments and in
docs/recroom-2023-client-api-complete.md.
Presence
- matchmake/none means "online, in no room" (its only caller is
TryGoToOfflineRoom, which ignores the reply). The server used to keep the
old room or invent a dorm, so the next heartbeat looked out of sync and
the client cancelled its load to chase that room.
- The join watchdog no longer clears presence 30 s into a long room load;
it is a 3 minute backstop that drops the room only.
- One fan-out (PresenceBroadcastService) for every transition: deferred
joins, matchmake/none, logout, failed joins, visibility changes, SignalR
connect/disconnect. isOnline is sent on its own; pushes never go to the
subject, because a self push moves the player.
- Online-status visibility (Public / Friends / Favorites / Appear offline)
is stored and honoured by GET /player and by the live pushes.
Profile
- PUT account/me/{emoji,personalpronouns,identityflags,bannerimage} were
404; added with the client's real form keys and persisted (new Players
columns plus migration). Every account read now returns them, and edits
push AccountUpdate to everyone.
Outfit menu
- Saved outfits kept OutfitSelectionsV2 and CustomAvatarItems, so loading a
slot restores what was saved instead of a stale outfit.
- isCreationAllowedForAccount answers the {Success, Error} envelope the
client parses; minPriceForPublicItem is a bare int; gifts/generate accepts
form bodies; lockeditems returns the full owned-item shape; colourways no
longer share their base item's AvatarItemId.
RecNetResult writes
- About 40 writes look status-only but parse the body as {Success, Error}.
An empty 200 showed "Failed to ..." after the server applied the change,
and a body without Success threw "Error message cannot be null". Clubs,
announcements, keepsakes, comments/read, subscriptions, purchase offers
and privacy settings now answer RecNetResult.
- Login token and device-code grants are real; remoteauth approves the
device code; createlogintoken returns a bare string.
- New: incentivizedreferrals/referrals, account/bulk/{email,phone},
GET strings, account/me/confirmphone, PlayMenuCarouselOrdering_1.
Route inventory and coverage test extended to match.
README now describes the 2023 branch.
147 lines
6.6 KiB
C#
147 lines
6.6 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
|
|
namespace DorkNet.Server.Data.Entities;
|
|
|
|
public class PlayerEntity
|
|
{
|
|
public long Id { get; set; }
|
|
|
|
// Stable per-installation identifier the client sends as `deviceId` in
|
|
// platformlogin / account-create form bodies. We deliberately use this
|
|
// (not `platformId`) so accounts are NOT keyed to Steam IDs — anyone
|
|
// running the client gets a unique persistent account based on the
|
|
// hardware-derived device hash, regardless of which Steam emulator
|
|
// (Goldberg, none, real Steam) is in front. Indexed unique below.
|
|
[MaxLength(128)]
|
|
public string DeviceId { get; set; } = string.Empty;
|
|
|
|
// Last-seen platform info — informational, NOT part of account identity.
|
|
public int LastPlatform { get; set; } = 0;
|
|
|
|
[MaxLength(128)]
|
|
public string LastPlatformId { get; set; } = string.Empty;
|
|
|
|
[MaxLength(64)]
|
|
public string Username { get; set; } = string.Empty;
|
|
|
|
[MaxLength(64)]
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
|
|
[MaxLength(512)]
|
|
public string Bio { get; set; } = string.Empty;
|
|
|
|
public int Level { get; set; } = 1;
|
|
public int XP { get; set; } = 0;
|
|
public int Reputation { get; set; } = 0;
|
|
public bool IsVerified { get; set; } = false;
|
|
|
|
// New accounts default to non-developer. Admins grant the badge
|
|
// privilege via the SPA's Players → Profile flags tab on a
|
|
// per-player basis (used to default to true while we were
|
|
// bootstrapping the emulator; flipped off now that the build is
|
|
// stable and we don't want every walk-in to spawn with the dev
|
|
// console unlocked).
|
|
public bool IsDeveloper { get; set; } = false;
|
|
// Mirrors IsDeveloper for the overhead badge slider — the 2020 watch
|
|
// gates the in-settings "Developer Display Mode" slider on a single
|
|
// role check (`role/developer/{id}`), and the slider's positions
|
|
// render as "Community Team" / "Developer" above the player's head
|
|
// (verified in Cpp2IL_ISIL/.../PlayerUI.txt:9085-9099 — slider value
|
|
// 1 → "Community Team", 2 → "Developer"). Keep this field separate
|
|
// from IsDeveloper so admins can mark someone as community-team
|
|
// without granting full dev privileges; the role endpoint ORs both.
|
|
public bool IsCommunityTeam { get; set; } = false;
|
|
public bool CanReceiveInvites { get; set; } = true;
|
|
public bool IsJunior { get; set; } = false;
|
|
|
|
[MaxLength(256)]
|
|
public string? ProfileImageName { get; set; }
|
|
|
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
|
|
|
|
// ── Phase 1 account hardening ─────────────────────────────────────────
|
|
// Optional contact email. Nullable because the 2020 client never asks
|
|
// for one; reserved for future password-reset / admin-recovery flows.
|
|
[MaxLength(256)]
|
|
public string? Email { get; set; }
|
|
|
|
// When non-null AND `> DateTime.UtcNow`, BanCheckMiddleware short-circuits
|
|
// every authenticated request from this account with 401. Setting to a
|
|
// past timestamp acts as a "previously banned" audit marker without
|
|
// blocking access.
|
|
public DateTime? BannedUntil { get; set; }
|
|
|
|
// Privilege flag for `[AdminOnly]` endpoints (Phase 5). Defaults false;
|
|
// PlayerService bumps this to true when the very first account on a
|
|
// fresh DB is created so there's always at least one root admin.
|
|
public bool IsAdmin { get; set; } = false;
|
|
|
|
// Last IP that authenticated against this account. Updated by the
|
|
// request-tracing pipeline so we don't write IPs from anonymous paths.
|
|
[MaxLength(64)]
|
|
public string? LastIp { get; set; }
|
|
|
|
// The deviceId that originally created this account. Immutable after
|
|
// creation — exists for the audit trail in case the live DeviceId
|
|
// rotates (e.g., user re-installs and the hardware hash shifts; the
|
|
// account stays bound to whatever they typed in /account/create the
|
|
// first time).
|
|
[MaxLength(128)]
|
|
public string? CreatedFromDeviceId { get; set; }
|
|
|
|
// BCP-47 locale tag (e.g. "en-US"). Optional; informs future
|
|
// localisation work and is currently set from the client's accept-
|
|
// language header at login.
|
|
[MaxLength(16)]
|
|
public string? Locale { get; set; }
|
|
|
|
/// <summary>BCrypt hash of the account password. Null when the
|
|
/// account has never set one — the in-game settings screen calls
|
|
/// /account/me/haspassword and prompts the user to create one
|
|
/// before sensitive operations. Format: BCrypt's standard
|
|
/// `$2a$<cost>$<salt><hash>` so the verifier
|
|
/// reads cost + salt directly, no separate column needed.</summary>
|
|
[MaxLength(128)]
|
|
public string? PasswordHash { get; set; }
|
|
|
|
/// <summary>Player's birthday (date only; no timezone). Set during
|
|
/// signup and used to derive <see cref="IsJunior"/> on Junior cutoff
|
|
/// boundary crossing. Nullable for legacy accounts created before
|
|
/// the field existed.</summary>
|
|
public DateTime? Birthday { get; set; }
|
|
|
|
/// <summary>Optional phone number — used by the watch's
|
|
/// account-recovery flow. Stored verbatim, never validated.</summary>
|
|
[MaxLength(32)]
|
|
public string? Phone { get; set; }
|
|
|
|
// ── 2023 profile fields ──────────────────────────────────────────────
|
|
// Written by PUT account/me/{emoji,bannerimage,personalpronouns,
|
|
// identityflags} and read back on every account DTO (account/me,
|
|
// account/{id}, account/bulk, search, AccountUpdate push) under the
|
|
// keys displayEmoji / bannerImage / personalPronouns / identityFlags.
|
|
|
|
/// <summary>Emoji shown beside the display name ("" = none).</summary>
|
|
[MaxLength(32)]
|
|
public string DisplayEmoji { get; set; } = string.Empty;
|
|
|
|
/// <summary>Profile banner image name (a storage image the player
|
|
/// already uploaded, e.g. one of their photos). Null = no banner.</summary>
|
|
[MaxLength(256)]
|
|
public string? BannerImageName { get; set; }
|
|
|
|
/// <summary>Pronoun bitmask (client flags enum: SheHer=1, HeHim=2,
|
|
/// TheyThem=4, ZeHir=8, ZeZir=16, XeXem=32).</summary>
|
|
public int PersonalPronouns { get; set; } = 0;
|
|
|
|
/// <summary>Identity-flag bitmask (client flags enum: LGBTQIA=1,
|
|
/// Transgender=2, Bisexual=4, …).</summary>
|
|
public int IdentityFlags { get; set; } = 0;
|
|
|
|
// Navigation
|
|
public AvatarEntity? Avatar { get; set; }
|
|
public List<PlayerSettingEntity> Settings { get; set; } = [];
|
|
public List<RelationshipEntity> Relationships { get; set; } = [];
|
|
}
|