Partial of the roleplay player that implements ATM interactions: host RPCs to claim an ATM, open the screen on the owner, release it, and process a withdrawal or deposit after re-validating distance, ownership of the machine, account access, amount limits and balances. The result is sent back to the requesting player only.
namespace HorizonRP;
/// <summary>
/// Les opérations du distributeur (UI/AtmScreen.razor) : l'hôte revérifie tout — un
/// distributeur à portée, le compte (le sien), le montant (entier, positif, plafonné, couvert) —
/// puis répond au seul joueur par <see cref="AtmResult"/>, que l'écran attend pour afficher
/// « Prenez vos billets » ou le refus.
/// </summary>
public sealed partial class RoleplayPlayer
{
/// <summary>Le plus gros retrait (ou dépôt) d'une seule opération, comme le plafond d'une carte.</summary>
public const int AtmLimit = 5000;
/// <summary>
/// Touche E sur un distributeur : l'hôte le donne s'il est libre (Atm.TryTake), et seulement
/// alors l'écran s'ouvre chez le joueur (<see cref="AtmGranted"/>). Un seul joueur à la fois
/// (Julien, 31/10/2026) ; sinon un message, rien ne s'ouvre.
/// </summary>
[Rpc.Host]
public void RequestAtmOpen( Guid atmId )
{
if ( !CallerIsOwner() || !ProfileLoaded )
return;
var atm = Atm.All( Scene ).FirstOrDefault( x => x.Id == atmId );
if ( atm is null || WorldPosition.WithZ( 0 ).Distance( atm.WorldPosition.WithZ( 0 ) ) > Atm.Reach )
return;
if ( !Atm.TryTake( atm, this ) )
{
Notify( "Ce distributeur est déjà utilisé : patientez.", "erreur" );
return;
}
AtmGranted( atmId );
}
/// <summary>Chez le joueur : le distributeur est à lui, l'écran s'ouvre.</summary>
[Rpc.Owner]
public void AtmGranted( Guid atmId )
{
var atm = Atm.All( Scene ).FirstOrDefault( x => x.Id == atmId );
if ( atm is not null )
AtmScreen.Open( atm );
}
/// <summary>L'écran se ferme (carte rendue, éloigné, mort…) : le distributeur est libéré.</summary>
[Rpc.Host]
public void ReleaseAtm()
{
if ( CallerIsOwner() )
Atm.Release( this );
}
[Rpc.Host]
public void RequestAtm( string operation, int amount, string accountId )
{
if ( !CallerIsOwner() )
return;
var game = HorizonGame.Current;
string refus = null;
if ( game is null || !ProfileLoaded )
refus = "Service momentanément indisponible.";
else if ( !Atm.IsNear( this ) )
refus = "Vous êtes trop loin du distributeur.";
else if ( !Atm.Holds( this ) )
refus = "Ce distributeur est déjà utilisé.";
else if ( Atm.AccountsOf( this ).All( x => x.Id != accountId ) )
refus = "Ce compte n'est pas accessible avec cette carte.";
else if ( amount <= 0 || amount > AtmLimit )
refus = $"Montant non autorisé (de 1 à {Format.Money( AtmLimit )}).";
else if ( operation == "retrait" && amount > Atm.BalanceOf( this, accountId ) )
refus = "Solde insuffisant.";
else if ( operation == "depot" && amount > Wallet )
refus = "Vous n'avez pas autant d'espèces sur vous.";
else if ( operation is not ("retrait" or "depot") )
refus = "Opération inconnue.";
if ( refus is not null )
{
AtmResult( false, refus, Bank, Wallet );
return;
}
game.TryTransfer( this, amount, toBank: operation == "depot", notify: false );
AtmResult( true, "", Bank, Wallet );
}
/// <summary>Chez le joueur : la réponse de l'hôte, avec les soldes après l'opération.</summary>
[Rpc.Owner]
public void AtmResult( bool ok, string message, int bank, int wallet )
{
AtmScreen.Answer( ok, message, bank, wallet );
}
}