# Laundering System

Addon DarkRP de blanchiment d'argent sale via NPC. V1 = méthode NPC. Conçu pour permettre à d'autres addons d'ajouter des méthodes supplémentaires (laverie, business front, etc.) via `Laundering.Execute(ply, amount, opts)`.

## Dépendances

- **DBALib v0.8.0+** — `addons/_dbalib/`
- **dirtymoney_system** — `addons/dirtymoney_system/`
- **DarkRP** (framework)

## Installation

1. Copier le dossier `laundering_system/` dans `garrysmod/addons/`.
2. Redémarrer le serveur.
3. Spawner un NPC blanchisseur via `laundering_spawn_npc` (voir Commandes admin).

## Configuration

Fichier : `lua/laundering/sh_config.lua`

| Clé | Défaut | Description |
|-----|--------|-------------|
| `Tiers` | voir ci-dessous | Table des paliers de taux (extensible) |
| `Cooldown` | `30` | Secondes entre deux transactions par joueur |
| `MaxDistance` | `150` | Distance max au NPC pendant transaction (units) |
| `MinAmount` | `100` | Montant minimum par transaction |
| `MaxAmount` | `1000000000` | Montant maximum par transaction (1 milliard) |
| `DefaultDuration` | `5` | Durée de la progress bar (secondes) |
| `AllowedJobs` | `{}` | Liste de noms de jobs autorisés — vide = tous |
| `Logs` | `true` | Affichage des opérations en console serveur |

## Mécanique de taux stochastique

Pour chaque transaction, le système tire un nombre aléatoire `r` dans `[0, 1[` :

- **Si `r >= partialChance`** (65% du temps avec les defaults) : taux nominal appliqué (`baseRate`).
- **Si `r < partialChance`** (35% du temps) : taux dégradé, uniforme dans `[partialMin, partialMax]`.

**Exemple avec les valeurs par défaut :**
- Pour 100 000 argent sale avec `{ baseRate=0.80, partialChance=0.35, partialMin=0.50, partialMax=0.80 }` :
  - 65% de chance de récupérer **80 000** argent propre.
  - 35% de chance de récupérer entre **50 000** et **80 000** argent propre (distribution uniforme).

**Configuration multi-paliers (exemple V2) :**
```lua
Config.Tiers = {
    { min=1,       max=9999,    baseRate=0.90, partialChance=0.10, partialMin=0.75, partialMax=0.90 },
    { min=10000,   max=99999,   baseRate=0.80, partialChance=0.25, partialMin=0.60, partialMax=0.80 },
    { min=100000,  max=999999,  baseRate=0.75, partialChance=0.35, partialMin=0.50, partialMax=0.75 },
    { min=1000000, max=1000000000, baseRate=0.65, partialChance=0.50, partialMin=0.40, partialMax=0.65 },
}
```

## Commandes admin

Toutes ces commandes nécessitent le rang **SuperAdmin** en jeu, ou peuvent être exécutées via la console RCON.

### `laundering_spawn_npc`
Spawne un NPC blanchisseur à la position visée (trace POV).
- **In-game** : viser une surface et exécuter la commande.
- **Console RCON** : `laundering_spawn_npc <x> <y> <z> <yaw>`

### `laundering_remove_npc`
Supprime le NPC blanchisseur visé (trace POV). Supprime également la ligne SQLite correspondante.

### `laundering_list_npcs`
Affiche dans la console serveur tous les NPCs de la map courante avec leur id et position.

### `laundering_cleanup`
Détecte et supprime les entités `laundering_npc` orphelines (non enregistrées dans le système, ex: spawns manuels via toolgun).

## API publique (pour d'autres addons)

### `Laundering.Compute(amount, tiersOverride?)`
Fonction pure. Calcule le montant propre obtenu pour un montant sale donné.

```lua
-- Retourne : cleanAmount(int), isPartial(bool), tier(table|nil)
local clean, partial, tier = Laundering.Compute(100000)
print(clean, partial)  -- ex: 80000, false
```

### `Laundering.Execute(ply, amount, opts)`
Lance une transaction de blanchiment. Gère tout : prise de DirtyMoney, timer, refund sur cancel/mort/déco.

```lua
-- Retourne : success(bool), errorString(string|nil)
local ok, err = Laundering.Execute(ply, amount, {
    methodId          = "npc",          -- string libre, logué et envoyé aux hooks
    duration          = 5,              -- secondes de progress bar
    requiresProximity = someEntity,     -- entité : si invalide/trop loin à la fin → cancel + refund
    maxDistance       = 150,            -- units (default Config.MaxDistance)
    onComplete = function(ply, dirtyIn, cleanOut, isPartial)
        print("Succès !", cleanOut)
    end,
    onCancel = function(ply, amount, reason)
        print("Annulé :", reason)
    end,
})
```

### `Laundering.GetTierForAmount(amount, tiersOverride?)`
Retourne le tier applicable à un montant, ou nil.

## Hooks publics

### `Laundering.Started(ply, amount, methodId)`
Déclenché quand une transaction démarre (argent sale déjà prélevé). Non-bloquant.

```lua
hook.Add("Laundering.Started", "MonAddon.LaunderingStart", function(ply, amount, methodId)
    print(ply:Nick(), "commence à blanchir", amount, "via", methodId)
end)
```

### `Laundering.Completed(ply, dirtyInput, cleanOutput, isPartial, methodId)`
Déclenché quand une transaction réussit (argent propre déjà crédité).

```lua
hook.Add("Laundering.Completed", "MonAddon.LaunderingDone", function(ply, dirty, clean, partial, method)
    if partial then
        print("Résultat dégradé :", clean, "/", dirty)
    end
    -- Exemple : donner XP via dbalevel_system
    -- DBALevel.AddXP(ply, math.floor(dirty / 1000), "laundering")
end)
```

### `Laundering.Cancelled(ply, amount, reason, methodId)`
Déclenché lors de toute annulation. Le DirtyMoney est déjà remboursé quand ce hook se déclenche.
Raisons : `"player_cancel"` | `"died"` | `"disconnected"` | `"distance_lost"` | `"entity_lost"` | `"shutdown"` | `"no_tier"`

### `Laundering.PoliceAlert(ply, dirtyInput, cleanOutput, isPartial, methodId, pos)`
Déclenché stochastiquement APRÈS un cycle complété, selon le champ `policeChance`
du tier appliqué (0..1, défaut 0). `pos` = position de l'entité du blanchiment
(NPC, laverie, etc.) ou position du joueur en fallback. Le hook est non-bloquant
et purement informatif — un futur addon police peut s'y abonner pour spawn
des forces de l'ordre, déclencher une alerte radio, etc.

```lua
-- Dans un futur addon police
hook.Add("Laundering.PoliceAlert", "MyPoliceAddon", function(ply, dirty, clean, partial, methodId, pos)
    -- Spawn flics, alerte radio, marquer le joueur recherché, etc.
end)
```

Pour activer le risque police, set `policeChance` sur un palier (ex `policeChance = 0.15`)
dans `Config.Tiers` de `sh_config.lua`, ou via `tiersOverride` dans `Laundering.Execute`
(comme le fait l'addon laverie via `Config.PoliceChance`).

## Exemple — Addon V2 (laverie automatique)

```lua
-- sv_laundromat_hooks.lua (addon externe)
hook.Add("Use", "Laundromat.UseWashMachine", function(ent, activator)
    if not IsValid(activator) or not activator:IsPlayer() then return end
    if ent:GetClass() ~= "prop_laundromat_machine" then return end

    local amount = DirtyMoney.Get(activator)
    if amount <= 0 then return end

    local ok, err = Laundering.Execute(activator, amount, {
        methodId          = "laundromat",
        duration          = 30,
        requiresProximity = ent,
        maxDistance       = 80,
        onComplete = function(ply, dirtyIn, cleanOut, isPartial)
            -- Feedback custom de la laverie
            DarkRP.notify(ply, 0, 5, string.format("Linge propre : %d€", cleanOut))
        end,
    })

    if not ok then
        DarkRP.notify(activator, 4, 4, err or "Erreur blanchiment.")
    end
end)
```

## Limitations connues

- Le cooldown est en mémoire vive uniquement : il est remis à zéro au restart serveur (comportement voulu — acceptable V1).
- Les transactions en cours au moment d'un restart serveur sont annulées avec remboursement automatique du DirtyMoney (via le hook `ShutDown`).
- Pas de persistance des transactions elles-mêmes (acceptable : durée max = `DefaultDuration` secondes).
- Le check de proximité se fait à la FIN du timer, pas en continu (performance). Si le joueur s'éloigne pendant la transaction puis revient avant la fin, il sera quand même crédité.
