feat(city): add player marketplace, mission journal, character customization and presence

- Player-to-player marketplace with listings, buying and selling
- Mission system with progress tracking and money rewards
- Character appearance customization (skin, hair, face, beard, accessory)
- Multiplayer presence feed showing nearby citizens in near-real-time
- Admin world dashboard with live platform metrics
- Bus and train transportation with temporary ride state
- Mobile joystick controls and minimap HUD
- Education bonus applied to work salary
This commit is contained in:
2026-10-17 22:51:20 -03:00
parent 18778b3d26
commit a620f83dd8
30 changed files with 1822 additions and 10 deletions
+11 -1
View File
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Models\Building;
use App\Models\Character;
use App\Support\MissionTracker;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -102,6 +103,12 @@ class CityTick extends Command
return;
}
// Temporary bus/train rides expire after ride_until.
if ($character->ride_kind !== null && $character->ride_until !== null
&& $character->ride_until->isPast()) {
$character->forceFill(['ride_kind' => null, 'ride_until' => null])->save();
}
$elapsedMin = 1.0;
if ($character->last_tick_at !== null) {
$elapsedMin = max(0.5, $character->last_tick_at->diffInMinutes($now));
@@ -124,7 +131,9 @@ class CityTick extends Command
case Character::STATE_WORKING:
if ($character->job_id !== null) {
$salary = (float) ($character->job?->salary ?? 0);
$character->money = round((float) $character->money + ($salary / 30) * $elapsedMin, 2);
// Education boosts take-home pay up to +50% at 100% education.
$bonus = 1 + (min(100, (int) $character->education) / 200);
$character->money = round((float) $character->money + ($salary * $bonus / 30) * $elapsedMin, 2);
}
$character->energy = max(0, (int) $character->energy - (int) round(1.2 * $elapsedMin));
break;
@@ -132,6 +141,7 @@ class CityTick extends Command
case Character::STATE_STUDYING:
$character->education = min(100, (int) $character->education + (int) round(0.5 * $elapsedMin));
$character->energy = max(0, (int) $character->energy - (int) round(0.8 * $elapsedMin));
MissionTracker::sync($character, 'education');
break;
case Character::STATE_EXERCISING: