Files
City10/tests/Feature/PlazaExerciseTest.php
T
marcocosta10000 090b530481 feat(city): add plaza exercise system with health tracking
- Adds CityPlazas support class for deterministic public plaza generation with outdoor gym equipment
- Implements exercise and stop-exercise actions with proximity validation to equipment
- Introduces health (0-100) and exercise_count attributes to Character model
- Reduces hospital stay duration from two game days to one game hour
- Adds 3D gym equipment rendering (bench, bars, bike, treadmill) in city viewer
- Includes exercise state machine handling in world ticker with energy drain
- Adds comprehensive PlazaExerciseTest and updates HospitalAdmissionTest
- Provides localized UI strings for health HUD and exercise prompts
2026-10-17 22:10:50 -03:00

140 lines
4.3 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Character;
use App\Models\User;
use App\Support\CityPlazas;
use Database\Seeders\CityWorldSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* Public plaza gym equipment and health gains from exercise.
*/
class PlazaExerciseTest extends TestCase
{
use RefreshDatabase;
protected User $user;
protected Character $character;
/**
* Boot world fixtures.
*/
protected function setUp(): void
{
parent::setUp();
$this->seed(CityWorldSeeder::class);
$this->user = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
$this->character = Character::ensureForUser($this->user);
$this->actingAs($this->user);
}
/**
* City payload includes plazas with gym equipment.
*/
public function test_city_data_includes_plazas(): void
{
$this->getJson('/city/data')
->assertOk()
->assertJsonStructure([
'plazas' => [
['id', 'position', 'size', 'equipment' => [['id', 'kind', 'position']]],
],
]);
$this->assertNotEmpty(CityPlazas::forCity($this->character->city));
}
/**
* Exercising near equipment raises health and increments the counter.
*/
public function test_exercise_gains_health_near_equipment(): void
{
$eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0];
$this->character->forceFill([
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
'health' => 80,
'energy' => 90,
'state' => Character::STATE_AWAKE,
])->save();
$this->postJson('/city/actions/exercise', [
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
])->assertOk()
->assertJsonPath('character.health', 82)
->assertJsonPath('character.state', Character::STATE_EXERCISING);
$this->character->refresh();
$this->assertSame(82, (int) $this->character->health);
$this->assertSame(1, (int) $this->character->exercise_count);
$this->assertSame(88, (int) $this->character->energy);
}
/**
* Far from equipment the action is rejected.
*/
public function test_exercise_requires_nearby_equipment(): void
{
$eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0];
$this->character->forceFill([
'pos_x' => $eq['position'][0] + 50,
'pos_z' => $eq['position'][1] + 50,
'health' => 50,
])->save();
$this->postJson('/city/actions/exercise', [
'pos_x' => $eq['position'][0] + 50,
'pos_z' => $eq['position'][1] + 50,
])->assertStatus(422);
}
/**
* Max health refuses further gains but keeps the character healthy.
*/
public function test_exercise_stops_at_max_health(): void
{
$eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0];
$this->character->forceFill([
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
'health' => 100,
])->save();
$this->postJson('/city/actions/exercise', [
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
])->assertOk()
->assertJsonPath('character.health', 100);
$this->assertSame(0, (int) $this->character->fresh()->exercise_count);
}
/**
* stop-exercise returns the character to awake.
*/
public function test_stop_exercise_returns_awake(): void
{
$eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0];
$this->character->forceFill([
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
'health' => 70,
'state' => Character::STATE_AWAKE,
])->save();
$this->postJson('/city/actions/exercise', [
'pos_x' => $eq['position'][0],
'pos_z' => $eq['position'][1],
])->assertOk();
$this->postJson('/city/actions/stop-exercise')
->assertOk()
->assertJsonPath('character.state', Character::STATE_AWAKE);
}
}