422 lines
9.8 KiB
PHP
422 lines
9.8 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\CharacterStatus;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
/**
|
|
* @property int $id
|
|
* @property int $user_id
|
|
* @property string $name
|
|
* @property string $gender
|
|
* @property int $level
|
|
* @property int $experience
|
|
* @property int $money
|
|
* @property int $bank
|
|
* @property int $energy
|
|
* @property int $max_energy
|
|
* @property int $health
|
|
* @property int $max_health
|
|
* @property int $strength
|
|
* @property int $defense
|
|
* @property int $speed
|
|
* @property int $dexterity
|
|
* @property int $location_id
|
|
* @property int|null $faction_id
|
|
* @property \Illuminate\Support\Carbon|null $in_hospital_until
|
|
* @property \Illuminate\Support\Carbon|null $in_jail_until
|
|
* @property \Illuminate\Support\Carbon $last_action_at
|
|
* @property CharacterStatus $status
|
|
* @property \Illuminate\Support\Carbon $created_at
|
|
* @property \Illuminate\Support\Carbon $updated_at
|
|
*/
|
|
class Character extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'name',
|
|
'gender',
|
|
'level',
|
|
'experience',
|
|
'money',
|
|
'bank',
|
|
'energy',
|
|
'max_energy',
|
|
'health',
|
|
'max_health',
|
|
'strength',
|
|
'defense',
|
|
'speed',
|
|
'dexterity',
|
|
'location_id',
|
|
'faction_id',
|
|
'in_hospital_until',
|
|
'in_jail_until',
|
|
'last_action_at',
|
|
'status',
|
|
];
|
|
|
|
protected $casts = [
|
|
'in_hospital_until' => 'datetime',
|
|
'in_jail_until' => 'datetime',
|
|
'last_action_at' => 'datetime',
|
|
'status' => CharacterStatus::class,
|
|
];
|
|
|
|
/**
|
|
* Get the user that owns the character.
|
|
*/
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
/**
|
|
* Get the location where the character is.
|
|
*/
|
|
public function location(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Location::class);
|
|
}
|
|
|
|
/**
|
|
* Get the faction the character belongs to.
|
|
* Phase 2: Faction System
|
|
*/
|
|
// public function faction(): BelongsTo
|
|
// {
|
|
// return $this->belongsTo(Faction::class)->withDefault();
|
|
// }
|
|
|
|
/**
|
|
* Get the inventory items for the character.
|
|
*/
|
|
public function inventory(): HasMany
|
|
{
|
|
return $this->hasMany(Inventory::class);
|
|
}
|
|
|
|
/**
|
|
* Get the crime attempts for the character.
|
|
*/
|
|
public function crimeAttempts(): HasMany
|
|
{
|
|
return $this->hasMany(CrimeAttempt::class);
|
|
}
|
|
|
|
/**
|
|
* Get the activity logs for the character.
|
|
*/
|
|
public function activityLogs(): HasMany
|
|
{
|
|
return $this->hasMany(ActivityLog::class);
|
|
}
|
|
|
|
/**
|
|
* Check if character can perform actions
|
|
*/
|
|
public function canAct(): bool
|
|
{
|
|
return $this->status === CharacterStatus::ACTIVE && $this->health > 0;
|
|
}
|
|
|
|
/**
|
|
* Check if character is in hospital
|
|
*/
|
|
public function isInHospital(): bool
|
|
{
|
|
return $this->status === CharacterStatus::IN_HOSPITAL
|
|
|| ($this->in_hospital_until && $this->in_hospital_until->isFuture());
|
|
}
|
|
|
|
/**
|
|
* Check if character is in jail
|
|
*/
|
|
public function isInJail(): bool
|
|
{
|
|
return $this->status === CharacterStatus::IN_JAIL
|
|
|| ($this->in_jail_until && $this->in_jail_until->isFuture());
|
|
}
|
|
|
|
/**
|
|
* Check if character is alive
|
|
*/
|
|
public function isAlive(): bool
|
|
{
|
|
return $this->health > 0;
|
|
}
|
|
|
|
/**
|
|
* Get energy percentage
|
|
*/
|
|
public function getEnergyPercentageAttribute(): float
|
|
{
|
|
if ($this->max_energy <= 0) {
|
|
return 0;
|
|
}
|
|
return min(100, ($this->energy / $this->max_energy) * 100);
|
|
}
|
|
|
|
/**
|
|
* Get health percentage
|
|
*/
|
|
public function getHealthPercentageAttribute(): float
|
|
{
|
|
if ($this->max_health <= 0) {
|
|
return 0;
|
|
}
|
|
return min(100, ($this->health / $this->max_health) * 100);
|
|
}
|
|
|
|
/**
|
|
* Get total attack power
|
|
*/
|
|
public function getAttackPowerAttribute(): int
|
|
{
|
|
$base = $this->strength * config('game.skills.strength.attack_bonus', 0.5);
|
|
$weapon = $this->inventory()->where('equipped', true)->whereHas('item', function($q) {
|
|
$q->where('type', 'weapon');
|
|
})->first();
|
|
|
|
$weaponBonus = $weapon ? $weapon->item->attack_bonus : 0;
|
|
|
|
return (int) ($base + $weaponBonus + config('game.combat.base_damage', 5));
|
|
}
|
|
|
|
/**
|
|
* Get total defense
|
|
*/
|
|
public function getDefenseAttribute(): int
|
|
{
|
|
$base = $this->attributes['defense'] ?? 0;
|
|
$armor = $this->inventory()->where('equipped', true)->whereHas('item', function($q) {
|
|
$q->where('type', 'armor');
|
|
})->first();
|
|
|
|
$armorBonus = $armor ? $armor->item->defense_bonus : 0;
|
|
|
|
return $base + $armorBonus;
|
|
}
|
|
|
|
/**
|
|
* Get experience needed for next level
|
|
*/
|
|
public function getExperienceForNextLevelAttribute(): int
|
|
{
|
|
$base = config('game.leveling.exp_per_level', 1000);
|
|
$multiplier = config('game.leveling.exp_multiplier', 1.20);
|
|
|
|
if (config('game.leveling.exp_curve') === 'exponential') {
|
|
return (int) ($base * pow($multiplier, $this->level - 1));
|
|
}
|
|
|
|
return $base;
|
|
}
|
|
|
|
/**
|
|
* Check if character has enough energy for an action
|
|
*/
|
|
public function hasEnergy(int $amount): bool
|
|
{
|
|
return $this->energy >= $amount;
|
|
}
|
|
|
|
/**
|
|
* Consume energy
|
|
*/
|
|
public function consumeEnergy(int $amount): bool
|
|
{
|
|
if (!$this->hasEnergy($amount)) {
|
|
return false;
|
|
}
|
|
|
|
$this->energy -= $amount;
|
|
$this->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add energy (up to max)
|
|
*/
|
|
public function addEnergy(int $amount): void
|
|
{
|
|
$this->energy = min($this->max_energy, $this->energy + $amount);
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Set energy to max
|
|
*/
|
|
public function restoreEnergy(): void
|
|
{
|
|
$this->energy = $this->max_energy;
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Take damage
|
|
*/
|
|
public function takeDamage(int $amount): int
|
|
{
|
|
$defenseReduction = $this->defense * config('game.combat.defense_reduction', 0.5);
|
|
$actualDamage = max(1, $amount - $defenseReduction);
|
|
|
|
$this->health -= $actualDamage;
|
|
$this->save();
|
|
|
|
if ($this->health <= 0) {
|
|
$this->health = 0;
|
|
$this->status = CharacterStatus::DEAD;
|
|
$this->save();
|
|
}
|
|
|
|
return $actualDamage;
|
|
}
|
|
|
|
/**
|
|
* Heal health
|
|
*/
|
|
public function heal(int $amount): void
|
|
{
|
|
$this->health = min($this->max_health, $this->health + $amount);
|
|
|
|
if ($this->status === CharacterStatus::DEAD && $this->health > 0) {
|
|
$this->status = CharacterStatus::ACTIVE;
|
|
}
|
|
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Add experience
|
|
*/
|
|
public function addExperience(int $amount): void
|
|
{
|
|
$this->experience += $amount;
|
|
|
|
while ($this->experience >= $this->experience_for_next_level && $this->level < config('game.leveling.max_level', 100)) {
|
|
$this->levelUp();
|
|
}
|
|
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Level up the character
|
|
*/
|
|
public function levelUp(): void
|
|
{
|
|
$this->level++;
|
|
$this->experience -= $this->experience_for_next_level;
|
|
$this->max_health += 5;
|
|
$this->max_energy += 5;
|
|
$this->health = $this->max_health;
|
|
$this->energy = $this->max_energy;
|
|
$this->money += 1000;
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Add money
|
|
*/
|
|
public function addMoney(int $amount): void
|
|
{
|
|
$this->money += $amount;
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Remove money
|
|
*/
|
|
public function removeMoney(int $amount): bool
|
|
{
|
|
if ($this->money < $amount) {
|
|
return false;
|
|
}
|
|
|
|
$this->money -= $amount;
|
|
$this->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if character has enough money
|
|
*/
|
|
public function hasMoney(int $amount): bool
|
|
{
|
|
return $this->money >= $amount;
|
|
}
|
|
|
|
/**
|
|
* Deposit money to bank
|
|
*/
|
|
public function depositToBank(int $amount): bool
|
|
{
|
|
if (!$this->hasMoney($amount)) {
|
|
return false;
|
|
}
|
|
|
|
$maxBank = config('game.economy.max_bank', 10000000);
|
|
|
|
if ($this->bank + $amount > $maxBank) {
|
|
return false;
|
|
}
|
|
|
|
$this->money -= $amount;
|
|
$this->bank += $amount;
|
|
$this->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Withdraw money from bank
|
|
*/
|
|
public function withdrawFromBank(int $amount): bool
|
|
{
|
|
if ($this->bank < $amount) {
|
|
return false;
|
|
}
|
|
|
|
$this->bank -= $amount;
|
|
$this->money += $amount;
|
|
$this->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Apply daily bank interest
|
|
*/
|
|
public function applyBankInterest(): void
|
|
{
|
|
$interestRate = config('game.economy.bank_interest', 0.05);
|
|
$interest = (int) ($this->bank * $interestRate);
|
|
|
|
$maxBank = config('game.economy.max_bank', 10000000);
|
|
|
|
if ($this->bank + $interest > $maxBank) {
|
|
$interest = $maxBank - $this->bank;
|
|
}
|
|
|
|
$this->bank += $interest;
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Get the character's full name with level
|
|
*/
|
|
public function getFullNameAttribute(): string
|
|
{
|
|
return "{$this->name} (Lv. {$this->level})";
|
|
}
|
|
}
|