Files
marcocosta10000 79ce2a1825 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
2026-10-17 21:46:23 -03:00

114 lines
2.8 KiB
PHP
Executable File

<?php
namespace App\Models;
use Database\Factories\CityFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Geographic territory of a virtual city.
*
* Bounds drive street layout generation, movement clamping and construction
* limits. Administrators may expand bounds or create additional cities.
*/
#[Fillable([
'name', 'slug', 'bounds_min_x', 'bounds_max_x', 'bounds_min_z', 'bounds_max_z',
'is_active', 'expansions',
])]
class City extends Model
{
/** @use HasFactory<CityFactory, $this> */
use HasFactory;
/**
* Maximum number of territory expansions allowed per city.
*/
public const MAX_EXPANSIONS = 10;
/**
* Characters living in this city.
*
* @return HasMany<Character, $this>
*/
public function characters(): HasMany
{
return $this->hasMany(Character::class);
}
/**
* Buildings placed inside this city.
*
* @return HasMany<Building, $this>
*/
public function buildings(): HasMany
{
return $this->hasMany(Building::class);
}
/**
* Job positions offered in this city.
*
* @return HasMany<Job, $this>
*/
public function jobs(): HasMany
{
return $this->hasMany(Job::class);
}
/**
* Find the active city used as the default world.
*/
public static function active(): ?self
{
return self::query()->where('is_active', true)->orderBy('id')->first();
}
/**
* Expand territory eastward and northward by one grid step (40 units).
*
* Returns false when the expansion cap has been reached.
*/
public function expand(): bool
{
if ($this->expansions >= self::MAX_EXPANSIONS) {
return false;
}
$this->bounds_max_x = (float) $this->bounds_max_x + 40;
$this->bounds_max_z = (float) $this->bounds_max_z + 40;
$this->expansions = (int) $this->expansions + 1;
$this->save();
return true;
}
/**
* Determine whether a world coordinate lies inside the city bounds.
*/
public function contains(float $x, float $z): bool
{
return $x >= (float) $this->bounds_min_x && $x <= (float) $this->bounds_max_x
&& $z >= (float) $this->bounds_min_z && $z <= (float) $this->bounds_max_z;
}
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'bounds_min_x' => 'float',
'bounds_max_x' => 'float',
'bounds_min_z' => 'float',
'bounds_max_z' => 'float',
'is_active' => 'boolean',
'expansions' => 'integer',
];
}
}