- 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
135 lines
4.0 KiB
PHP
Executable File
135 lines
4.0 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Building;
|
|
use App\Models\Character;
|
|
use App\Models\Job;
|
|
use Database\Seeders\CityWorldSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* World ticker behaviour: needs, salary, construction progress.
|
|
*/
|
|
class CityTickTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* Seed world data.
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed(CityWorldSeeder::class);
|
|
}
|
|
|
|
/**
|
|
* Needs decay on the first tick for awake characters.
|
|
*/
|
|
public function test_tick_decays_needs_once(): void
|
|
{
|
|
$character = Character::factory()->create([
|
|
'state' => Character::STATE_AWAKE,
|
|
'energy' => 100,
|
|
'hunger' => 100,
|
|
'thirst' => 100,
|
|
'last_tick_at' => null,
|
|
]);
|
|
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
|
|
$character->refresh();
|
|
$this->assertLessThan(100, $character->hunger);
|
|
$this->assertLessThan(100, $character->thirst);
|
|
$this->assertNotNull($character->last_tick_at);
|
|
|
|
$hungerAfterFirst = $character->hunger;
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
$character->refresh();
|
|
$this->assertSame($hungerAfterFirst, $character->hunger, 'Second immediate tick must be a no-op');
|
|
}
|
|
|
|
/**
|
|
* Sleeping restores energy and wakes the character at full energy.
|
|
*/
|
|
public function test_tick_recovers_energy_while_sleeping(): void
|
|
{
|
|
$character = Character::factory()->create([
|
|
'state' => Character::STATE_SLEEPING,
|
|
'energy' => 50,
|
|
'last_tick_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
$character->refresh();
|
|
$this->assertGreaterThan(50, $character->energy);
|
|
|
|
// Force another tick window.
|
|
$character->forceFill(['last_tick_at' => now()->subMinute(), 'energy' => 95])->save();
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
$character->refresh();
|
|
$this->assertSame(100, $character->energy);
|
|
$this->assertSame(Character::STATE_AWAKE, $character->state);
|
|
}
|
|
|
|
/**
|
|
* Working characters accrue salary each tick.
|
|
*/
|
|
public function test_tick_pays_salary_while_working(): void
|
|
{
|
|
$character = Character::factory()->create([
|
|
'state' => Character::STATE_WORKING,
|
|
'money' => 100,
|
|
'last_tick_at' => now()->subMinute(),
|
|
]);
|
|
$character->job()->associate(Job::firstOrFail());
|
|
$character->save();
|
|
|
|
$before = (float) $character->money;
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
$character->refresh();
|
|
$this->assertGreaterThan($before, (float) $character->money);
|
|
}
|
|
|
|
/**
|
|
* Incomplete buildings advance and complete houses attach to the owner.
|
|
*/
|
|
public function test_tick_progresses_construction(): void
|
|
{
|
|
$character = Character::factory()->create();
|
|
$building = Building::create([
|
|
'city_id' => $character->city_id,
|
|
'owner_character_id' => $character->id,
|
|
'type' => Building::TYPE_HOUSE,
|
|
'name' => 'Casa',
|
|
'pos_x' => 20,
|
|
'pos_z' => -70,
|
|
'width' => 10,
|
|
'depth' => 10,
|
|
'height' => 5,
|
|
'is_complete' => false,
|
|
'build_progress' => 90,
|
|
]);
|
|
|
|
$this->artisan('city:tick')->assertSuccessful();
|
|
$building->refresh();
|
|
$character->refresh();
|
|
|
|
$this->assertTrue($building->is_complete);
|
|
$this->assertSame(100, $building->build_progress);
|
|
$this->assertSame($building->id, $character->house_id);
|
|
}
|
|
|
|
/**
|
|
* Scheduler registers the ticker every minute.
|
|
*/
|
|
public function test_scheduler_includes_city_tick(): void
|
|
{
|
|
$this->artisan('schedule:list')
|
|
->expectsOutputToContain('city:tick')
|
|
->assertSuccessful();
|
|
}
|
|
}
|