Files
City10/routes/web.php
T
marcocosta10000 59963de995 feat(city): add bump-to-talk conversations with persistent context
Implements proximity-based street conversations between player characters and peers (human or agentic NPC). New PersonTalk model, controller, and migrations store daily message threads that persist across sessions and provide context for future interactions. Agent replies include backoff throttling and fallback persona handling to avoid dead-end chats.

Frontend changes refactor the chat panel to support both character and agent peers, opening automatically when the player moves within range, polling for replies, rendering day separators, and displaying rate-limit warnings. Styling updated to a transparent blurred panel. Translations extended with new hint text and rate-limit messages across en, es, and pt_BR.
2026-10-31 17:59:22 -03:00

160 lines
8.1 KiB
PHP
Executable File

<?php
use App\Http\Controllers\Web\AccountController;
use App\Http\Controllers\Web\Admin\AdminWorldController;
use App\Http\Controllers\Web\Admin\CitizenAdminController;
use App\Http\Controllers\Web\Admin\CityAdminController;
use App\Http\Controllers\Web\Admin\StoreAdminController;
use App\Http\Controllers\Web\AuthController;
use App\Http\Controllers\Web\CharacterChatController;
use App\Http\Controllers\Web\CharacterCustomizeController;
use App\Http\Controllers\Web\ChatController;
use App\Http\Controllers\Web\CityActionController;
use App\Http\Controllers\Web\CityMarketController;
use App\Http\Controllers\Web\CityPresenceController;
use App\Http\Controllers\Web\CitySocialController;
use App\Http\Controllers\Web\CityViewerController;
use App\Http\Controllers\Web\DashboardController;
use App\Http\Controllers\Web\LanguageController;
use App\Http\Controllers\Web\LegalController;
use App\Http\Controllers\Web\PersonTalkController;
use App\Http\Middleware\EnsureTermsAccepted;
use App\Http\Middleware\EnsureUserIsAdmin;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| City10 Web Routes
|--------------------------------------------------------------------------
|
| Public pages (welcome, legal documents, language switch) are open to
| guests. The dashboard and consent screen require an authenticated web
| session and are blocked until the current legal documents are accepted.
|
*/
// ---------------------------------------------------------------------------
// Public pages
// ---------------------------------------------------------------------------
Route::get('/', function () {
return view('welcome');
})->name('welcome');
Route::get('/lang', [LanguageController::class, 'switch'])->name('lang.switch');
// Legal documents (public, readable without an account).
Route::get('/legal/{type}', [LegalController::class, 'show'])
->whereIn('type', ['terms', 'privacy', 'cookies'])
->name('legal.show');
// ---------------------------------------------------------------------------
// Guest authentication
// ---------------------------------------------------------------------------
Route::middleware('guest')->group(function (): void {
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:10,1');
Route::get('/register', [AuthController::class, 'showRegister'])->name('register');
Route::post('/register', [AuthController::class, 'register'])->middleware('throttle:10,1');
});
// ---------------------------------------------------------------------------
// Authenticated area
// ---------------------------------------------------------------------------
Route::middleware('auth')->group(function (): void {
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
// Pending registration wait screen (reachable before full approval).
Route::get('/account/pending', [AccountController::class, 'pending'])->name('account.pending');
// Consent screen is reachable even before terms are accepted (by design).
Route::get('/terms/accept', [LegalController::class, 'acceptForm'])->name('terms.accept');
Route::post('/terms/accept', [LegalController::class, 'accept'])->name('terms.accept.store');
Route::get('/terms/history', [LegalController::class, 'history'])->name('terms.history');
// Main platform features (blocked until terms are accepted).
Route::middleware(EnsureTermsAccepted::class)->group(function (): void {
Route::get('/dashboard', DashboardController::class)->name('dashboard');
// 3D city viewer (Phase 3).
Route::get('/city', CityViewerController::class)->name('city.viewer');
Route::get('/city/data', [CityViewerController::class, 'data'])->name('city.data');
// Citizen lifecycle actions inside the world.
Route::post('/city/actions/position', [CityActionController::class, 'position'])
->middleware('throttle:60,1')
->name('city.actions.position');
Route::post('/city/actions/{action}', [CityActionController::class, 'handle'])
->middleware('throttle:90,1')
->name('city.actions.handle');
// Character social graph and chat.
Route::post('/city/social/{verb}', [CitySocialController::class, 'handle'])
->middleware('throttle:30,1')
->name('city.social.handle');
Route::get('/city/social/inbox', [CitySocialController::class, 'inbox'])->name('city.social.inbox');
Route::post('/city/messages/{character}', [CharacterChatController::class, 'send'])
->middleware('throttle:60,1')
->name('city.messages.send');
Route::get('/city/messages/{character}', [CharacterChatController::class, 'index'])->name('city.messages.index');
// Multiplayer presence + marketplace + journal + cosmetics.
Route::get('/city/presence', CityPresenceController::class)
->middleware('throttle:60,1')
->name('city.presence');
Route::get('/city/market', [CityMarketController::class, 'index'])->name('city.market.index');
Route::post('/city/market/sell', [CityMarketController::class, 'sell'])
->middleware('throttle:30,1')
->name('city.market.sell');
Route::post('/city/market/buy', [CityMarketController::class, 'buy'])
->middleware('throttle:30,1')
->name('city.market.buy');
Route::get('/city/customize', [CharacterCustomizeController::class, 'show'])->name('city.customize.show');
Route::put('/city/customize', [CharacterCustomizeController::class, 'update'])
->middleware('throttle:20,1')
->name('city.customize.update');
// NPC chat (SSE stream + message POST).
Route::post('/city/chat/{agent}', [ChatController::class, 'send'])
->middleware('throttle:30,1')
->name('city.chat.send');
Route::get('/city/chat/{agent}/stream', [ChatController::class, 'stream'])->name('city.chat.stream');
// Daily street conversations (bump-to-talk) with persistent context.
Route::get('/talk/person', [PersonTalkController::class, 'index'])
->middleware('throttle:120,1')
->name('talk.person.index');
Route::post('/talk/person', [PersonTalkController::class, 'send'])
->middleware('throttle:30,1')
->name('talk.person.send');
});
});
// ---------------------------------------------------------------------------
// Admin area (administrator role required, on top of auth + terms)
// ---------------------------------------------------------------------------
Route::middleware(['auth', EnsureTermsAccepted::class, EnsureUserIsAdmin::class])
->prefix('admin')
->name('admin.')
->group(function (): void {
Route::get('/stores', [StoreAdminController::class, 'index'])->name('stores.index');
Route::get('/stores/create', [StoreAdminController::class, 'create'])->name('stores.create');
Route::post('/stores', [StoreAdminController::class, 'store'])->name('stores.store');
Route::get('/stores/{store}/edit', [StoreAdminController::class, 'edit'])->name('stores.edit');
Route::put('/stores/{store}', [StoreAdminController::class, 'update'])->name('stores.update');
Route::delete('/stores/{store}', [StoreAdminController::class, 'destroy'])->name('stores.destroy');
Route::get('/citizens', [CitizenAdminController::class, 'index'])->name('citizens.index');
Route::post('/citizens/{user}/approve', [CitizenAdminController::class, 'approve'])->name('citizens.approve');
Route::post('/citizens/{user}/ban', [CitizenAdminController::class, 'ban'])->name('citizens.ban');
Route::get('/cities', [CityAdminController::class, 'index'])->name('cities.index');
Route::post('/cities', [CityAdminController::class, 'store'])->name('cities.store');
Route::post('/cities/{city}/expand', [CityAdminController::class, 'expand'])->name('cities.expand');
Route::get('/world', [AdminWorldController::class, 'index'])->name('world.index');
});