194 lines
6.7 KiB
Markdown
194 lines
6.7 KiB
Markdown
# Nerve Stat + Gym Training — Design Spec
|
|||
|
|
|
||
|
|
**Date:** 2026-06-28
|
||
|
|
**Status:** Approved
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
Add a **nerve** stat that acts as a daily crime-volume cap alongside the existing per-attempt energy cost. Add a **gym** where players spend energy to train the four combat stats (strength, defense, speed, dexterity), subject to a per-stat daily session cap.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Model
|
||
|
|
|
||
|
|
### `characters` table — new columns
|
||
|
|
|
||
|
|
| Column | Type | Default | Notes |
|
||
|
|
|--------|------|---------|-------|
|
||
|
|
| `nerve` | int | 100 | Current nerve |
|
||
|
|
| `max_nerve` | int | 100 | Max nerve; grows +2 per level-up |
|
||
|
|
| `nerve_regen_at` | timestamp | null | Tracks next trickle tick |
|
||
|
|
|
||
|
|
### `crimes` table — new column
|
||
|
|
|
||
|
|
| Column | Type | Default | Notes |
|
||
|
|
|--------|------|---------|-------|
|
||
|
|
| `nerve_cost` | int | 1 | Nerve consumed per attempt |
|
||
|
|
|
||
|
|
### `crime_attempts` table — new column
|
||
|
|
|
||
|
|
| Column | Type | Default | Notes |
|
||
|
|
|--------|------|---------|-------|
|
||
|
|
| `nerve_cost` | int | 1 | Nerve actually spent (for logs) |
|
||
|
|
|
||
|
|
### `gym_sessions` table — new table
|
||
|
|
|
||
|
|
| Column | Type | Notes |
|
||
|
|
|--------|------|-------|
|
||
|
|
| `id` | bigint PK | |
|
||
|
|
| `character_id` | bigint FK | |
|
||
|
|
| `stat` | enum(strength, defense, speed, dexterity) | |
|
||
|
|
| `sessions_today` | int | Resets lazily when `reset_date` < today |
|
||
|
|
| `reset_date` | date | Date sessions_today was last reset |
|
||
|
|
| `created_at` | timestamp | |
|
||
|
|
| `updated_at` | timestamp | |
|
||
|
|
|
||
|
|
Unique constraint on `(character_id, stat)`. One row per character per stat, upserted on each training session.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Nerve Mechanics
|
||
|
|
|
||
|
|
### Role
|
||
|
|
Nerve is a secondary resource gating **crime volume**. Each crime attempt costs nerve (default 1, configurable per-crime). Energy still gates individual attempts as today. A player with full energy but 0 nerve cannot commit crimes.
|
||
|
|
|
||
|
|
### Regen
|
||
|
|
- **Trickle:** +1 nerve every 5 minutes (same interval as energy). Handled by a new `ProcessNerveRegen` artisan command on the scheduler.
|
||
|
|
- **Midnight refill:** `nerve` set to `max_nerve` for all characters at `00:00` daily. Handled by a new `NerveMidnightRefill` artisan command.
|
||
|
|
|
||
|
|
### Max nerve scaling
|
||
|
|
Starts at 100. Grows by **+2 per level-up** (slower than max_energy's +5, since nerve caps daily crime volume rather than individual actions).
|
||
|
|
|
||
|
|
### Crime integration
|
||
|
|
`CrimeController@attempt` checks nerve after the existing energy check. If insufficient, returns an error. On success, deducts nerve and records `nerve_cost` on the `CrimeAttempt`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Gym Mechanics
|
||
|
|
|
||
|
|
### Route
|
||
|
|
`POST /game/gym/train` → `GymController@train`
|
||
|
|
`GET /game/gym` → `GymController@index` (the gym page)
|
||
|
|
|
||
|
|
### Training flow
|
||
|
|
1. Player selects a stat and submits the Train form.
|
||
|
|
2. Controller verifies: character is active (not in jail/hospital), has ≥ 20 energy, daily cap not reached for that stat.
|
||
|
|
3. `TrainingService::train($character, $stat)` executes:
|
||
|
|
- Deduct 20 energy from character.
|
||
|
|
- Increment the chosen stat by 1.
|
||
|
|
- Upsert `gym_sessions` row: if `reset_date` < today, reset `sessions_today` to 0 first; then increment.
|
||
|
|
4. Flash success/error message, redirect back to gym page.
|
||
|
|
|
||
|
|
### Daily cap
|
||
|
|
5 sessions per stat per day (configurable via `config/game.php` `gym.daily_cap`). Reset is **lazy** — no scheduled command; checked on each train attempt by comparing `reset_date` to today.
|
||
|
|
|
||
|
|
### Stat gain
|
||
|
|
Flat +1 per session. No diminishing returns in this iteration.
|
||
|
|
|
||
|
|
### Energy cost
|
||
|
|
20 energy per session. Uses existing `check.energy:gym` middleware (requires adding `gym` to `config/game.php` `energy.action_costs`).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Config (`config/game.php`)
|
||
|
|
|
||
|
|
Add two new blocks:
|
||
|
|
|
||
|
|
```php
|
||
|
|
'nerve' => [
|
||
|
|
'start_amount' => 100,
|
||
|
|
'max' => 100,
|
||
|
|
'regen_rate' => 1, // nerve per tick
|
||
|
|
'regen_interval' => 300, // seconds (5 minutes)
|
||
|
|
'midnight_refill' => true,
|
||
|
|
],
|
||
|
|
|
||
|
|
'gym' => [
|
||
|
|
'energy_cost' => 20,
|
||
|
|
'daily_cap' => 5, // sessions per stat per day
|
||
|
|
'stat_gain' => 1, // stat points per session
|
||
|
|
],
|
||
|
|
```
|
||
|
|
|
||
|
|
Also add to existing `energy.action_costs`:
|
||
|
|
```php
|
||
|
|
'gym' => 20,
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Character Model Changes
|
||
|
|
|
||
|
|
Add to `$fillable`: `nerve`, `max_nerve`, `nerve_regen_at`
|
||
|
|
Add to `$casts`: `nerve_regen_at` → `datetime`
|
||
|
|
|
||
|
|
New methods (mirroring existing energy methods):
|
||
|
|
- `hasNerve(int $amount): bool`
|
||
|
|
- `consumeNerve(int $amount): bool`
|
||
|
|
- `addNerve(int $amount): void`
|
||
|
|
- `getNervePercentageAttribute(): float`
|
||
|
|
|
||
|
|
`levelUp()` updated to increment `max_nerve` by 2.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## New Files
|
||
|
|
|
||
|
|
| File | Purpose |
|
||
|
|
|------|---------|
|
||
|
|
| `app/Http/Controllers/Game/GymController.php` | `index()` and `train()` actions |
|
||
|
|
| `app/Services/Game/TrainingService.php` | Training logic, daily cap check, stat increment |
|
||
|
|
| `app/Models/GymSession.php` | Eloquent model for `gym_sessions` |
|
||
|
|
| `app/Console/Commands/Game/ProcessNerveRegen.php` | Trickle regen command |
|
||
|
|
| `app/Console/Commands/Game/NerveMidnightRefill.php` | Midnight full refill command |
|
||
|
|
| `resources/views/game/gym/index.blade.php` | Gym page (dark crime theme) |
|
||
|
|
| `database/migrations/..._add_nerve_to_characters_table.php` | nerve columns on characters |
|
||
|
|
| `database/migrations/..._add_nerve_cost_to_crimes_table.php` | nerve_cost on crimes |
|
||
|
|
| `database/migrations/..._add_nerve_cost_to_crime_attempts_table.php` | nerve_cost on crime_attempts |
|
||
|
|
| `database/migrations/..._create_gym_sessions_table.php` | gym_sessions table |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Existing Files Modified
|
||
|
|
|
||
|
|
| File | Change |
|
||
|
|
|------|--------|
|
||
|
|
| `config/game.php` | Add nerve + gym blocks; add gym to energy.action_costs |
|
||
|
|
| `app/Models/Character.php` | Add nerve fields, methods, levelUp() update |
|
||
|
|
| `app/Http/Controllers/Game/CrimeController.php` | Add nerve check + deduction in `attempt()` |
|
||
|
|
| `app/Services/Game/CrimeService.php` | Record nerve_cost on CrimeAttempt |
|
||
|
|
| `routes/game.php` | Add gym routes |
|
||
|
|
| `bootstrap/app.php` (or Kernel) | Register two new scheduled commands |
|
||
|
|
| `resources/views/game/layouts/game.blade.php` (or nav partial) | Add Gym link to sidebar |
|
||
|
|
| `resources/views/game/partials/` | Add nerve bar partial (mirrors energy-bar) |
|
||
|
|
| `resources/views/game/dashboard.blade.php` | Show nerve bar alongside energy + health |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## UI — Gym Page
|
||
|
|
|
||
|
|
Dark crime theme, consistent with existing pages. Shows:
|
||
|
|
- Four stat cards (Strength, Defense, Speed, Dexterity)
|
||
|
|
- Each card: current stat value, sessions used today / daily cap, energy cost per session
|
||
|
|
- Train button: disabled (greyed) if capped or insufficient energy, active otherwise
|
||
|
|
- Flash message area for success/failure feedback
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Scheduler Registration
|
||
|
|
|
||
|
|
```php
|
||
|
|
Schedule::command('game:nerve-regen')->everyFiveMinutes();
|
||
|
|
Schedule::command('game:nerve-midnight-refill')->dailyAt('00:00');
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Out of Scope (this iteration)
|
||
|
|
|
||
|
|
- Diminishing returns on stat gains
|
||
|
|
- Money cost for training
|
||
|
|
- Nerve cost > 1 for specific crimes (column added, seeded at 1 for all)
|
||
|
|
- Nerve displayed on crime cards (can add later)
|