feat(world): add persistent citizen simulation with needs, jobs, and social features
- 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
This commit is contained in:
Executable
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Character;
|
||||
use App\Models\CharacterMessage;
|
||||
use App\Models\Relationship;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\CityWorldSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Social graph and character chat coverage.
|
||||
*/
|
||||
class CitySocialTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Seed world fixtures.
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->seed(CityWorldSeeder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Friendship request flows from pending to accepted.
|
||||
*/
|
||||
public function test_friend_request_and_accept(): void
|
||||
{
|
||||
$a = Character::factory()->create();
|
||||
$b = Character::factory()->create();
|
||||
$userA = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
|
||||
$a->user_id = $userA->id;
|
||||
$a->save();
|
||||
|
||||
$this->actingAs($userA)
|
||||
->postJson('/city/social/friend', ['character_id' => $b->id])
|
||||
->assertOk();
|
||||
|
||||
$rel = Relationship::where('type', Relationship::TYPE_FRIEND)
|
||||
->where('character_a_id', $a->id)
|
||||
->where('character_b_id', $b->id)
|
||||
->first();
|
||||
$this->assertNotNull($rel);
|
||||
$this->assertSame(Relationship::STATUS_PENDING, $rel->status);
|
||||
|
||||
$userB = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
|
||||
$b->user_id = $userB->id;
|
||||
$b->save();
|
||||
|
||||
$this->actingAs($userB)
|
||||
->postJson('/city/social/accept-friend', ['relationship_id' => $rel->id])
|
||||
->assertOk();
|
||||
|
||||
$this->assertSame(Relationship::STATUS_ACCEPTED, $rel->fresh()->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marriage requires an accepted friendship.
|
||||
*/
|
||||
public function test_marry_requires_friendship(): void
|
||||
{
|
||||
$userA = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
|
||||
$a = Character::ensureForUser($userA);
|
||||
$b = Character::factory()->create(['city_id' => $a->city_id]);
|
||||
|
||||
$this->actingAs($userA)
|
||||
->postJson('/city/social/marry', ['character_id' => $b->id])
|
||||
->assertStatus(422);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spouses can have an NPC child linked to both parents.
|
||||
*/
|
||||
public function test_child_creates_npc_character(): void
|
||||
{
|
||||
$userA = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
|
||||
$a = Character::ensureForUser($userA);
|
||||
$b = Character::factory()->create(['city_id' => $a->city_id, 'name' => 'Parceiro']);
|
||||
|
||||
Relationship::create([
|
||||
'character_a_id' => $a->id,
|
||||
'character_b_id' => $b->id,
|
||||
'type' => Relationship::TYPE_FRIEND,
|
||||
'status' => Relationship::STATUS_ACCEPTED,
|
||||
]);
|
||||
Relationship::create([
|
||||
'character_a_id' => $a->id,
|
||||
'character_b_id' => $b->id,
|
||||
'type' => Relationship::TYPE_SPOUSE,
|
||||
'status' => Relationship::STATUS_ACCEPTED,
|
||||
]);
|
||||
|
||||
$before = Character::count();
|
||||
$this->actingAs($userA)
|
||||
->postJson('/city/social/child', ['character_id' => $b->id])
|
||||
->assertOk();
|
||||
$this->assertSame($before + 1, Character::count());
|
||||
|
||||
$child = Character::orderByDesc('id')->first();
|
||||
$this->assertNull($child->user_id);
|
||||
$this->assertSame(2, Relationship::where('type', Relationship::TYPE_PARENT_CHILD)
|
||||
->where('character_a_id', $child->id)->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages persist between characters.
|
||||
*/
|
||||
public function test_character_chat_persists(): void
|
||||
{
|
||||
$userA = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]);
|
||||
$a = Character::ensureForUser($userA);
|
||||
$b = Character::factory()->create(['city_id' => $a->city_id]);
|
||||
|
||||
$this->actingAs($userA)
|
||||
->postJson("/city/messages/{$b->id}", ['body' => 'Olá vizinho!'])
|
||||
->assertOk();
|
||||
|
||||
$this->assertSame(1, CharacterMessage::count());
|
||||
|
||||
$this->actingAs($userA)
|
||||
->getJson("/city/messages/{$b->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('me', $a->id)
|
||||
->assertJsonPath('messages.0.body', 'Olá vizinho!');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user