138 lines
4.1 KiB
Markdown
138 lines
4.1 KiB
Markdown
# Escape from Jail — Design Spec
|
||
|
||
**Date:** 2026-06-28
|
||
**Status:** Approved
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
Add a **Bust Out** mechanic to the jail page. A jailed character can spend cash to attempt an escape. Success rate is based on `dexterity + speed` stats. Success releases immediately; failure multiplies remaining sentence by 1.5×. Cash is always deducted regardless of outcome.
|
||
|
||
---
|
||
|
||
## Data Model
|
||
|
||
No new tables or columns required. Uses existing `characters.status` and `characters.in_jail_until`.
|
||
|
||
---
|
||
|
||
## Bust-Out Mechanics
|
||
|
||
### Cost
|
||
1000 cash (from `config('game.jail.breakout_cost')`). Deducted whether the attempt succeeds or fails.
|
||
|
||
### Success Rate Formula
|
||
```
|
||
success_rate = min(
|
||
config('game.jail.bust_out_max_chance'),
|
||
config('game.jail.breakout_chance') + (dexterity + speed) * config('game.jail.bust_out_stat_multiplier')
|
||
)
|
||
```
|
||
|
||
With defaults:
|
||
- `breakout_chance` = 0.05 (existing)
|
||
- `bust_out_stat_multiplier` = 0.003 (new)
|
||
- `bust_out_max_chance` = 0.80 (new)
|
||
|
||
A fresh character (dex=5, speed=5) starts at **8%**. A well-trained character (dex=50, speed=50) reaches **35%**. Max possible is **80%**.
|
||
|
||
### On Success
|
||
- Deduct `breakout_cost` cash
|
||
- Set `status → active`
|
||
- Set `in_jail_until → null`
|
||
- Redirect to `game.dashboard` with success flash
|
||
|
||
### On Failure
|
||
- Deduct `breakout_cost` cash
|
||
- Multiply remaining jail time by `sentence_multiplier` (1.5×):
|
||
`in_jail_until = now()->addSeconds(ceil($character->in_jail_until->diffInSeconds(now()) * 1.5))`
|
||
- Redirect back to `game.jail` with failure flash showing new release time
|
||
|
||
---
|
||
|
||
## Config Changes (`config/game.php`)
|
||
|
||
Add two keys to the existing `jail` block:
|
||
|
||
```php
|
||
'jail' => [
|
||
'sentence_length' => 3600,
|
||
'breakout_chance' => 0.05,
|
||
'breakout_cost' => 1000,
|
||
'sentence_multiplier' => 1.5,
|
||
'bust_out_stat_multiplier' => 0.003, // per combined dex+speed point
|
||
'bust_out_max_chance' => 0.80, // cap at 80%
|
||
],
|
||
```
|
||
|
||
---
|
||
|
||
## New Files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `app/Services/Game/JailService.php` | `bustOut(Character): array`, `sendToJail(Character, int): void`, `releaseFromJail(Character): void` |
|
||
| `app/Http/Controllers/Game/JailController.php` | `bustOut(Request): RedirectResponse` |
|
||
|
||
### `JailService` interface
|
||
|
||
```php
|
||
/**
|
||
* @return array{success: bool, message: string}
|
||
*/
|
||
public function bustOut(Character $character): array
|
||
|
||
public function sendToJail(Character $character, int $seconds): void
|
||
|
||
public function releaseFromJail(Character $character): void
|
||
```
|
||
|
||
`bustOut()` returns `['success' => true/false, 'message' => string]`. The message is a human-readable outcome description suitable for a flash notice.
|
||
|
||
---
|
||
|
||
## Modified Files
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `config/game.php` | Add `bust_out_stat_multiplier` and `bust_out_max_chance` to `jail` block |
|
||
| `resources/views/game/jail.blade.php` | Add Bust Out card below status card |
|
||
| `routes/game.php` | Add `POST /game/jail/bust-out` → `JailController@bustOut` |
|
||
|
||
---
|
||
|
||
## Routes
|
||
|
||
```php
|
||
Route::middleware(['auth'])->group(function () {
|
||
Route::post('/game/jail/bust-out', [JailController::class, 'bustOut'])->name('game.jail.bust-out');
|
||
});
|
||
```
|
||
|
||
The bust-out route lives in the same `auth`-only group as the existing jail view route (no `verify.character` needed — jailed characters can't have characters, but do need auth).
|
||
|
||
---
|
||
|
||
## UI — Jail Page
|
||
|
||
Dark crime theme, consistent with existing pages.
|
||
|
||
**Bust Out card** sits below the existing status card. Contains:
|
||
- Success percentage (calculated from character stats, shown as e.g. "12% chance")
|
||
- Cash cost ("Costs $1,000")
|
||
- "Bust Out" button — disabled with tooltip if character has < 1000 cash
|
||
- CSRF-protected POST form
|
||
|
||
**Flash messages:**
|
||
- Success: "You escaped! You're free." → redirects to dashboard
|
||
- Failure: "Bust failed — your sentence was extended to [new release time]." → stays on jail page
|
||
|
||
---
|
||
|
||
## Out of Scope (this iteration)
|
||
|
||
- Busting out another player (requires PvP)
|
||
- Nerve/energy cost for bust attempts
|
||
- Per-attempt cooldown (failure penalty is time extension only)
|
||
- Bust attempt history / activity log entry (can add later) |