Wallet/Documents.cs

Static helper that generates fictional ID and bank card details for a wallet from a player's SteamID: citizen ID, Luhn-valid card number, CVV, expiry dates and job badges. Values are derived by hashing so they stay the same everywhere without storage.

namespace HorizonRP;

/// <summary>
/// Les numéros des papiers du portefeuille, tirés du SteamID comme le numéro de
/// téléphone : toujours les mêmes, sans rien stocker, et lisibles sur toutes les
/// machines (on peut montrer sa carte d'identité à un autre joueur).
/// </summary>
public static class Documents
{
	/// <summary>Le nom de l'État fictif d'Union City, imprimé sur les papiers.</summary>
	public const string State = "ÉTAT DE NEW HORIZON";

	public const string BankName = "HZ BANK";

	/// <summary>Durées de validité, en années, à partir de la première venue en ville.</summary>
	public const int IdValidYears = 8;
	public const int CardValidYears = 4;

	static ulong Mix( ulong steamId, ulong salt )
	{
		var x = steamId + salt * 0x9E3779B97F4A7C15UL;
		x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9UL;
		x = (x ^ (x >> 27)) * 0x94D049BB133111EBUL;
		return x ^ (x >> 31);
	}

	/// <summary>« UC4K7Z2QX » : UC (Union City) et sept caractères.</summary>
	public static string CitizenId( ulong steamId )
	{
		const string alphabet = "ABCDEFGHJKLMNPRSTUVWXYZ23456789";
		var x = Mix( steamId, 1 );
		var id = new System.Text.StringBuilder( "UC" );
		for ( var i = 0; i < 7; i++ )
		{
			id.Append( alphabet[(int)(x % (ulong)alphabet.Length)] );
			x /= (ulong)alphabet.Length;
		}
		return id.ToString();
	}

	/// <summary>Seize chiffres commençant par 4, avec une clé de Luhn juste, comme une vraie carte.</summary>
	public static string CardNumber( ulong steamId )
	{
		var digits = "4" + (Mix( steamId, 2 ) % 100_000_000_000_000UL).ToString( "00000000000000" );
		var sum = 0;
		for ( var i = 0; i < 15; i++ )
		{
			var d = digits[14 - i] - '0';
			if ( i % 2 == 0 )
			{
				d *= 2;
				if ( d > 9 ) d -= 9;
			}
			sum += d;
		}
		digits += ((10 - sum % 10) % 10).ToString();
		return $"{digits[..4]} {digits[4..8]} {digits[8..12]} {digits[12..]}";
	}

	public static string Cvv( ulong steamId ) => (Mix( steamId, 3 ) % 1000).ToString( "000" );

	/// <summary>« AAAA-MM-JJ » + n ans, en « JJ/MM/AAAA ».</summary>
	public static string PlusYears( string isoDate, int years )
	{
		if ( !DateTime.TryParse( isoDate, out var date ) )
			return "—";
		return date.AddYears( years ).ToString( "dd/MM/yyyy" );
	}

	/// <summary>« MM/YY » : la fin de validité d'une carte bancaire.</summary>
	public static string ValidThru( string isoDate )
	{
		if ( !DateTime.TryParse( isoDate, out var date ) )
			return "—";
		return date.AddYears( CardValidYears ).ToString( "MM/yy" );
	}

	/// <summary>Le badge de métier, pour ceux qui en ont un.</summary>
	public static (string Agency, string Title)? BadgeOf( string jobId ) => jobId switch
	{
		"policier" => ("POLICE D'UNION CITY", "INSIGNE D'AGENT"),
		"medecin" => ("HÔPITAL GÉNÉRAL D'UNION CITY", "PERSONNEL MÉDICAL"),
		"maire" => ("MAIRIE D'UNION CITY", "MAIRE"),
		"mecanicien" => ("UNION CITY MOTORS", "BADGE D'EMPLOYÉ"),
		"staff" => ("HORIZON RP", "STAFF"),
		_ => null,
	};
}