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:
@@ -10,6 +10,7 @@ use App\Models\InventoryItem;
|
||||
use App\Models\Job;
|
||||
use App\Models\Transaction;
|
||||
use App\Support\CityPlazas;
|
||||
use App\Support\MissionTracker;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -100,10 +101,69 @@ class CityActionController extends Controller
|
||||
'apply-job' => $this->applyJob($request, $character),
|
||||
'exercise' => $this->exercise($request, $character),
|
||||
'stop-exercise' => $this->stopExercise($character),
|
||||
'ride' => $this->ride($request, $character),
|
||||
default => response()->json(['message' => __('city.action_unknown')], 422),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Board a nearby bus stop or train for a short ride.
|
||||
*/
|
||||
private function ride(Request $request, Character $character): JsonResponse
|
||||
{
|
||||
if ($character->isHospitalized()) {
|
||||
return response()->json(['message' => __('city.hospital_blocked')], 422);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'kind' => ['required', 'in:bus,train'],
|
||||
'pos_x' => ['nullable', 'numeric'],
|
||||
'pos_z' => ['nullable', 'numeric'],
|
||||
]);
|
||||
|
||||
$x = array_key_exists('pos_x', $data) ? (float) $data['pos_x'] : (float) $character->pos_x;
|
||||
$z = array_key_exists('pos_z', $data) ? (float) $data['pos_z'] : (float) $character->pos_z;
|
||||
|
||||
// Must be near a road (bus) or the train corridor (train).
|
||||
$city = $character->city;
|
||||
if ($data['kind'] === 'bus') {
|
||||
$nearRoad = false;
|
||||
for ($vx = ceil(((float) $city->bounds_min_x) / 40) * 40; $vx <= $city->bounds_max_x; $vx += 40) {
|
||||
if (abs($x - $vx) < 8) {
|
||||
$nearRoad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $nearRoad) {
|
||||
for ($vz = ceil(((float) $city->bounds_min_z) / 40) * 40; $vz <= $city->bounds_max_z; $vz += 40) {
|
||||
if (abs($z - $vz) < 8) {
|
||||
$nearRoad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! $nearRoad) {
|
||||
return response()->json(['message' => __('city.ride_need_stop')], 422);
|
||||
}
|
||||
} elseif (abs($z - ((float) $city->bounds_max_z + 14)) > 20) {
|
||||
return response()->json(['message' => __('city.ride_need_stop')], 422);
|
||||
}
|
||||
|
||||
$character->forceFill([
|
||||
'ride_kind' => $data['kind'],
|
||||
'ride_until' => now()->addSeconds(45),
|
||||
'state' => Character::STATE_AWAKE,
|
||||
'pos_x' => $x,
|
||||
'pos_z' => $z,
|
||||
'is_online' => true,
|
||||
])->save();
|
||||
|
||||
return response()->json([
|
||||
'message' => $data['kind'] === 'train' ? __('city.ride_train_ok') : __('city.ride_bus_ok'),
|
||||
'character' => $this->characterData($character),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* One exercise rep on nearby plaza gym equipment: +health, −energy.
|
||||
*
|
||||
@@ -155,9 +215,12 @@ class CityActionController extends Controller
|
||||
'pos_z' => $z,
|
||||
])->save();
|
||||
|
||||
$missionDones = MissionTracker::sync($character, 'exercise_count');
|
||||
|
||||
return response()->json([
|
||||
'message' => __('city.exercise_ok'),
|
||||
'equipment' => $nearby,
|
||||
'missions_completed' => $missionDones,
|
||||
'character' => $this->characterData($character),
|
||||
]);
|
||||
}
|
||||
@@ -476,6 +539,8 @@ class CityActionController extends Controller
|
||||
'build_progress' => 0,
|
||||
]);
|
||||
|
||||
MissionTracker::sync($character->fresh(), 'buildings');
|
||||
|
||||
return response()->json([
|
||||
'message' => __('city.build_started'),
|
||||
'building' => [
|
||||
@@ -569,6 +634,14 @@ class CityActionController extends Controller
|
||||
'pos_x' => (float) $character->pos_x,
|
||||
'pos_z' => (float) $character->pos_z,
|
||||
'hospitalized_until' => $character->hospitalized_until?->toIso8601String(),
|
||||
'face' => (int) $character->face,
|
||||
'beard' => (int) $character->beard,
|
||||
'accessory' => (int) $character->accessory,
|
||||
'skin' => (int) $character->skin,
|
||||
'hair' => (int) $character->hair,
|
||||
'shirt' => (int) $character->shirt,
|
||||
'ride_kind' => $character->ride_kind,
|
||||
'ride_until' => $character->ride_until?->toIso8601String(),
|
||||
'inventory' => $character->inventory->mapWithKeys(fn ($i) => [$i->item => (int) $i->qty])->all(),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user