feat(world): add persistent citizen simulation with needs, jobs, and social features

- Adds Character model with survival needs (hunger, thirst, energy) and state machine
- Implements city:tick world ticker for offline progression every minute
- Adds city territory expansion and building construction system
- Introduces job economy, inventory, and transaction audit trail
- Adds social graph (friends, marriage, children) and character chat
- Implements admin approval workflow for new registrations
- Adds hospital admission system for traffic accidents
- Includes comprehensive feature tests and world seeders
This commit is contained in:
2026-10-17 21:46:23 -03:00
parent 79d12fa2f4
commit 79ce2a1825
132 changed files with 11809 additions and 1036 deletions
+56
View File
@@ -0,0 +1,56 @@
@extends('layouts.app')
@section('title', __('admin.cities.title'))
@section('content')
<div style="max-width: 960px; margin: 0 auto;">
<div class="card" style="margin-bottom:1.25rem;">
<h1 style="margin-top:0; color:#fff;">🗺️ {{ __('admin.cities.title') }}</h1>
@if (session('status'))
<p style="color:#4ade80;">{{ session('status') }}</p>
@endif
@if ($errors->any())
<p style="color:var(--danger);">{{ $errors->first() }}</p>
@endif
<p style="color:var(--muted);">{{ __('admin.cities.hint') }}</p>
<form method="POST" action="{{ route('admin.cities.store') }}" style="display:flex; gap:.6rem; margin-top:1rem; flex-wrap:wrap;">
@csrf
<input type="text" name="name" required maxlength="120" placeholder="{{ __('admin.cities.name') }}"
style="flex:1; min-width:200px; padding:.6rem .8rem; border-radius:.55rem; border:1px solid var(--border); background:#0b1220; color:var(--text);">
<button type="submit" class="btn btn-primary">{{ __('admin.cities.create') }}</button>
</form>
</div>
<div class="card">
<table style="width:100%; border-collapse:collapse; color:var(--muted);">
<thead>
<tr style="text-align:left; border-bottom:1px solid var(--border);">
<th style="padding:.5rem;">{{ __('admin.cities.name') }}</th>
<th style="padding:.5rem;">{{ __('admin.cities.bounds') }}</th>
<th style="padding:.5rem;">{{ __('admin.cities.expansions') }}</th>
<th style="padding:.5rem;">{{ __('admin.citizens.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($cities as $city)
<tr style="border-bottom:1px solid var(--border);">
<td style="padding:.5rem; color:#fff;">{{ $city->name }} @if($city->is_active)<span class="badge badge-user">ativo</span>@endif</td>
<td style="padding:.5rem;">
X [{{ $city->bounds_min_x }}, {{ $city->bounds_max_x }}] ·
Z [{{ $city->bounds_min_z }}, {{ $city->bounds_max_z }}]
</td>
<td style="padding:.5rem;">{{ $city->expansions }}/{{ \App\Models\City::MAX_EXPANSIONS }}</td>
<td style="padding:.5rem;">
<form method="POST" action="{{ route('admin.cities.expand', $city) }}">
@csrf
<button type="submit" class="btn">{{ __('admin.cities.expand') }}</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endsection
+56
View File
@@ -0,0 +1,56 @@
@extends('layouts.app')
@section('title', __('admin.citizens.title'))
@section('content')
<div style="max-width: 960px; margin: 0 auto;">
<div class="card" style="margin-bottom:1.25rem;">
<h1 style="margin-top:0; color:#fff;">👥 {{ __('admin.citizens.title') }}</h1>
@if (session('status'))
<p style="color:#4ade80;">{{ session('status') }}</p>
@endif
<p style="color:var(--muted);">{{ __('admin.citizens.hint') }}</p>
</div>
<div class="card">
<table style="width:100%; border-collapse:collapse; color:var(--muted);">
<thead>
<tr style="text-align:left; border-bottom:1px solid var(--border);">
<th style="padding:.5rem;">{{ __('admin.citizens.name') }}</th>
<th style="padding:.5rem;">{{ __('Email') }}</th>
<th style="padding:.5rem;">{{ __('admin.citizens.status') }}</th>
<th style="padding:.5rem;">{{ __('admin.citizens.character') }}</th>
<th style="padding:.5rem;">{{ __('admin.citizens.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($users as $u)
<tr style="border-bottom:1px solid var(--border);">
<td style="padding:.5rem; color:#fff;">{{ $u->name }}</td>
<td style="padding:.5rem;">{{ $u->email }}</td>
<td style="padding:.5rem;">
<span class="badge {{ $u->status === 'active' ? 'badge-user' : 'badge-admin' }}">{{ $u->status }}</span>
</td>
<td style="padding:.5rem;">{{ $u->character?->name ?? '—' }}</td>
<td style="padding:.5rem; display:flex; gap:.4rem; flex-wrap:wrap;">
@if ($u->status === 'pending')
<form method="POST" action="{{ route('admin.citizens.approve', $u) }}">
@csrf
<button type="submit" class="btn btn-primary">{{ __('admin.citizens.approve') }}</button>
</form>
@endif
@if ($u->status !== 'banned' && $u->id !== auth()->id())
<form method="POST" action="{{ route('admin.citizens.ban', $u) }}">
@csrf
<button type="submit" class="btn">{{ __('admin.citizens.ban') }}</button>
</form>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
<div style="margin-top:1rem;">{{ $users->links() }}</div>
</div>
</div>
@endsection
View File
View File
View File
+17
View File
@@ -0,0 +1,17 @@
@extends('layouts.app')
@section('title', __('auth.pending_title'))
@section('content')
<div style="max-width: 480px; margin: 0 auto; text-align: center;">
<div class="card">
<h2 style="margin-top:0; color:#fff;">⏳ {{ __('auth.pending_title') }}</h2>
<p style="color:var(--muted);">{{ __('auth.pending_body') }}</p>
<p style="color:var(--muted); font-size:.9rem;">{{ auth()->user()?->email }}</p>
<form method="POST" action="{{ route('logout') }}" style="margin-top:1.5rem;">
@csrf
<button type="submit" class="btn">{{ __('Logout') }}</button>
</form>
</div>
</div>
@endsection
View File
+151 -871
View File
File diff suppressed because it is too large Load Diff
View File
+2
View File
@@ -109,6 +109,8 @@
<a href="{{ route('city.viewer') }}">{{ __('nav.city') }}</a>
@if (auth()->user()->isAdmin())
<a href="{{ route('admin.stores.index') }}" class="badge badge-admin" style="text-decoration:none;">{{ __('nav.admin') }}</a>
<a href="{{ route('admin.citizens.index') }}">{{ __('nav.citizens') }}</a>
<a href="{{ route('admin.cities.index') }}">{{ __('nav.cities') }}</a>
@endif
<form method="POST" action="{{ route('logout') }}">
@csrf
View File
View File
View File