first commit

This commit is contained in:
Doar Mihai
2026-06-27 14:38:18 +03:00
commit 96d317480b
165 changed files with 20061 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $character_id
* @property string $action
* @property string|null $description
* @property string|null $ip_address
* @property string|null $user_agent
* @property \Illuminate\Support\Carbon $created_at
*/
class ActivityLog extends Model
{
use HasFactory;
protected $fillable = [
'character_id',
'action',
'description',
'ip_address',
'user_agent',
];
/**
* Get the character associated with this activity log.
*/
public function character(): BelongsTo
{
return $this->belongsTo(Character::class);
}
/**
* Create a new activity log entry
*/
public static function log(Character $character, string $action, string $description = null, string $ipAddress = null, string $userAgent = null): self
{
return self::create([
'character_id' => $character->id,
'action' => $action,
'description' => $description,
'ip_address' => $ipAddress,
'user_agent' => $userAgent,
]);
}
/**
* Log a crime attempt
*/
public static function logCrimeAttempt(Character $character, Crime $crime, bool $success, int $reward = 0): self
{
$result = $success ? 'success' : 'failure';
$description = "Attempted crime: {$crime->name} - {$result}";
if ($success) {
$description .= " (Reward: \${$reward})";
}
return self::log($character, 'crime_attempt', $description);
}
/**
* Log money transaction
*/
public static function logMoneyTransaction(Character $character, string $type, int $amount, string $reason): self
{
$action = match($type) {
'earned' => 'money_earned',
'spent' => 'money_spent',
'deposited' => 'money_deposited',
'withdrew' => 'money_withdrew',
default => 'money_transaction',
};
$description = "\${$amount} {$type} - {$reason}";
return self::log($character, $action, $description);
}
/**
* Log level up
*/
public static function logLevelUp(Character $character, int $newLevel): self
{
return self::log(
$character,
'level_up',
"Reached level {$newLevel}"
);
}
/**
* Log character creation
*/
public static function logCharacterCreated(Character $character): self
{
return self::log(
$character,
'character_created',
"New character created: {$character->name}"
);
}
}
+421
View File
@@ -0,0 +1,421 @@
<?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->defense;
$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})";
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace App\Models;
use App\Enums\CrimeDifficulty;
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 string $name
* @property string $slug
* @property string|null $description
* @property CrimeDifficulty $difficulty
* @property float $success_rate
* @property int $reward_min
* @property int $reward_max
* @property int $experience
* @property int $energy_cost
* @property int $cooldown
* @property int|null $requires_level
* @property int|null $location_id
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
*/
class Crime extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'difficulty',
'success_rate',
'reward_min',
'reward_max',
'experience',
'energy_cost',
'cooldown',
'requires_level',
'location_id',
];
protected $casts = [
'difficulty' => CrimeDifficulty::class,
'success_rate' => 'float',
];
/**
* Get the location where this crime can be committed.
*/
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
/**
* Get the crime attempts for this crime.
*/
public function attempts(): HasMany
{
return $this->hasMany(CrimeAttempt::class);
}
/**
* Check if a character can attempt this crime
*/
public function canBeAttemptedBy(Character $character): bool
{
// Check level requirement
if ($this->requires_level && $character->level < $this->requires_level) {
return false;
}
// Check energy
if ($character->energy < $this->energy_cost) {
return false;
}
// Check location (if crime is location-specific)
if ($this->location_id && $character->location_id !== $this->location_id) {
return false;
}
return true;
}
/**
* Calculate the success rate for a specific character
*/
public function calculateSuccessRate(Character $character): float
{
$baseRate = $this->success_rate;
$difficultyModifier = $this->difficulty->successModifier();
$levelModifier = $character->level * 0.01; // +1% per level
$rate = $baseRate + $difficultyModifier + $levelModifier;
// Clamp between 10% and 95%
return max(0.10, min(0.95, $rate));
}
/**
* Calculate the reward for a successful crime
*/
public function calculateReward(Character $character): int
{
$baseReward = random_int($this->reward_min, $this->reward_max);
$difficultyMultiplier = $this->difficulty->rewardMultiplier();
$levelMultiplier = 1 + ($character->level * 0.01); // +1% per level
return (int) ($baseReward * $difficultyMultiplier * $levelMultiplier);
}
/**
* Calculate the experience reward
*/
public function calculateExperience(Character $character): int
{
$baseExp = $this->experience;
$levelMultiplier = 1 + ($character->level * 0.01);
return (int) ($baseExp * $levelMultiplier);
}
/**
* Get the display name for the difficulty
*/
public function getDifficultyLabelAttribute(): string
{
return $this->difficulty->label();
}
/**
* Get the color class for the difficulty badge
*/
public function getDifficultyColorAttribute(): string
{
return $this->difficulty->color();
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $character_id
* @property int $crime_id
* @property bool $success
* @property int $reward
* @property int $experience_gained
* @property \Illuminate\Support\Carbon $attempted_at
*/
class CrimeAttempt extends Model
{
use HasFactory;
protected $fillable = [
'character_id',
'crime_id',
'success',
'reward',
'experience_gained',
'attempted_at',
];
protected $casts = [
'success' => 'boolean',
'attempted_at' => 'datetime',
];
public $timestamps = false;
/**
* Get the character who attempted this crime.
*/
public function character(): BelongsTo
{
return $this->belongsTo(Character::class);
}
/**
* Get the crime that was attempted.
*/
public function crime(): BelongsTo
{
return $this->belongsTo(Crime::class);
}
/**
* Check if the crime was successful
*/
public function isSuccessful(): bool
{
return $this->success;
}
/**
* Check if the crime failed
*/
public function isFailed(): bool
{
return !$this->success;
}
}
+165
View File
@@ -0,0 +1,165 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $character_id
* @property int $item_id
* @property int $quantity
* @property bool $equipped
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
*/
class Inventory extends Model
{
use HasFactory;
protected $fillable = [
'character_id',
'item_id',
'quantity',
'equipped',
];
protected $casts = [
'equipped' => 'boolean',
];
/**
* Get the character that owns this inventory item.
*/
public function character(): BelongsTo
{
return $this->belongsTo(Character::class);
}
/**
* Get the item.
*/
public function item(): BelongsTo
{
return $this->belongsTo(Item::class);
}
/**
* Add items to inventory
*/
public static function addItem(Character $character, Item $item, int $quantity = 1): self
{
$inventory = self::firstOrNew([
'character_id' => $character->id,
'item_id' => $item->id,
]);
$inventory->quantity += $quantity;
$inventory->save();
return $inventory;
}
/**
* Remove items from inventory
*/
public function remove(int $quantity = 1): bool
{
if ($this->quantity < $quantity) {
return false;
}
$this->quantity -= $quantity;
if ($this->quantity <= 0) {
$this->delete();
} else {
$this->save();
}
return true;
}
/**
* Equip the item
*/
public function equip(): bool
{
if (!$this->item->canEquip()) {
return false;
}
// Unequip any other item of the same type
$this->character->inventory()
->where('equipped', true)
->whereHas('item', function($q) {
$q->where('type', $this->item->type);
})
->update(['equipped' => false]);
$this->equipped = true;
$this->save();
return true;
}
/**
* Unequip the item
*/
public function unequip(): void
{
$this->equipped = false;
$this->save();
}
/**
* Use the item (consume it)
*/
public function use(Character $character): bool
{
if (!$this->item->canUse()) {
return false;
}
if (!$this->item->canBeUsedBy($character)) {
return false;
}
// Apply item effects
$this->applyEffects($character);
// Remove one from inventory
return $this->remove(1);
}
/**
* Apply the item's effects to the character
*/
protected function applyEffects(Character $character): void
{
$item = $this->item;
if ($item->health_bonus > 0) {
$character->heal($item->health_bonus);
}
if ($item->energy_bonus > 0) {
$character->addEnergy($item->energy_bonus);
}
// Add money if it's a currency item
if ($item->type === ItemType::CURRENCY && $item->value > 0) {
$character->addMoney($item->value);
}
}
/**
* Get the total value of this inventory item
*/
public function getTotalValueAttribute(): int
{
return $this->quantity * $this->item->value;
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace App\Models;
use App\Enums\ItemType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $id
* @property string $name
* @property string $slug
* @property string|null $description
* @property ItemType $type
* @property int $value
* @property int|null $buy_price
* @property int|null $sell_price
* @property int|null $requires_level
* @property int $attack_bonus
* @property int $defense_bonus
* @property int $health_bonus
* @property int $energy_bonus
* @property bool $is_tradeable
* @property bool $is_usable
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
*/
class Item extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'type',
'value',
'buy_price',
'sell_price',
'requires_level',
'attack_bonus',
'defense_bonus',
'health_bonus',
'energy_bonus',
'is_tradeable',
'is_usable',
];
protected $casts = [
'type' => ItemType::class,
'is_tradeable' => 'boolean',
'is_usable' => 'boolean',
];
/**
* Get the characters that have this item in their inventory.
*/
public function characters(): BelongsToMany
{
return $this->belongsToMany(Character::class, 'inventories')
->withPivot('quantity', 'equipped')
->withTimestamps();
}
/**
* Get the shops that sell this item.
*/
public function shops(): BelongsToMany
{
return $this->belongsToMany(Shop::class, 'shop_items')
->withPivot('stock', 'price')
->withTimestamps();
}
/**
* Check if this item can be equipped
*/
public function canEquip(): bool
{
return $this->type->canEquip();
}
/**
* Check if this item can be used/consumed
*/
public function canUse(): bool
{
return $this->is_usable && $this->type->canUse();
}
/**
* Check if a character can use this item (level requirement)
*/
public function canBeUsedBy(Character $character): bool
{
if ($this->requires_level && $character->level < $this->requires_level) {
return false;
}
return true;
}
/**
* Get the sell price or default to half of buy price
*/
public function getSellPriceAttribute(): int
{
if ($this->sell_price > 0) {
return $this->sell_price;
}
return (int) ($this->buy_price * 0.5);
}
/**
* Check if this is a weapon
*/
public function isWeapon(): bool
{
return $this->type === ItemType::WEAPON;
}
/**
* Check if this is armor
*/
public function isArmor(): bool
{
return $this->type === ItemType::ARMOR;
}
/**
* Check if this is a consumable
*/
public function isConsumable(): bool
{
return $this->type === ItemType::CONSUMABLE;
}
/**
* Get the display name for the item type
*/
public function getTypeLabelAttribute(): string
{
return $this->type->label();
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $id
* @property string $name
* @property string $slug
* @property string|null $description
* @property int $travel_cost
* @property int|null $requires_level
* @property int|null $faction_control
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
*/
class Location extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'travel_cost',
'requires_level',
'faction_control',
];
/**
* Get the characters at this location.
*/
public function characters(): HasMany
{
return $this->hasMany(Character::class);
}
/**
* Get the shops at this location.
*/
public function shops(): HasMany
{
return $this->hasMany(Shop::class);
}
/**
* Get the crimes available at this location.
*/
public function crimes(): HasMany
{
return $this->hasMany(Crime::class);
}
/**
* Check if a character can travel to this location
*/
public function canTravel(Character $character): bool
{
// Check level requirement
if ($this->requires_level && $character->level < $this->requires_level) {
return false;
}
// Check if character has enough money
if ($character->money < $this->travel_cost) {
return false;
}
return true;
}
/**
* Get the travel cost from another location to this one
*/
public function getTravelCostFrom(Location $from): int
{
// For now, just use this location's travel cost
// In a more advanced system, we'd calculate based on distance
return $this->travel_cost;
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $user_id
* @property string $type
* @property array|null $data
* @property \Illuminate\Support\Carbon|null $read_at
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
*/
class Notification extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'type',
'data',
'read_at',
];
protected $casts = [
'data' => 'array',
'read_at' => 'datetime',
];
/**
* Get the user who received this notification.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Check if the notification has been read
*/
public function isRead(): bool
{
return $this->read_at !== null;
}
/**
* Check if the notification is unread
*/
public function isUnread(): bool
{
return $this->read_at === null;
}
/**
* Mark the notification as read
*/
public function markAsRead(): void
{
$this->read_at = now();
$this->save();
}
/**
* Mark the notification as unread
*/
public function markAsUnread(): void
{
$this->read_at = null;
$this->save();
}
/**
* Create a new notification
*/
public static function createForUser(User $user, string $type, array $data = []): self
{
return self::create([
'user_id' => $user->id,
'type' => $type,
'data' => $data,
]);
}
/**
* Send a system notification
*/
public static function sendSystem(User $user, string $message): self
{
return self::createForUser($user, 'system', [
'message' => $message,
]);
}
/**
* Send a crime success notification
*/
public static function sendCrimeSuccess(User $user, Crime $crime, int $reward): self
{
return self::createForUser($user, 'crime_success', [
'crime_name' => $crime->name,
'reward' => $reward,
]);
}
/**
* Send a crime failure notification
*/
public static function sendCrimeFailure(User $user, Crime $crime): self
{
return self::createForUser($user, 'crime_failure', [
'crime_name' => $crime->name,
]);
}
/**
* Get the notification message
*/
public function getMessageAttribute(): string
{
return match($this->type) {
'system' => $this->data['message'] ?? 'System notification',
'crime_success' => "Crime successful: {$this->data['crime_name']}! You earned \${$this->data['reward']}",
'crime_failure' => "Crime failed: {$this->data['crime_name']}.",
default => $this->type,
};
}
/**
* Get the notification icon
*/
public function getIconAttribute(): string
{
return match($this->type) {
'system' => 'information-circle',
'crime_success' => 'check-circle',
'crime_failure' => 'x-circle',
'money' => 'currency-dollar',
'level_up' => 'arrow-up-circle',
default => 'bell',
};
}
/**
* Get the notification color
*/
public function getColorAttribute(): string
{
return match($this->type) {
'system' => 'blue',
'crime_success' => 'green',
'crime_failure' => 'red',
'money' => 'yellow',
'level_up' => 'purple',
default => 'gray',
};
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
/**
* Get the character for this user.
*/
public function character(): HasOne
{
return $this->hasOne(Character::class);
}
/**
* Get all characters for this user (for future multi-character support).
*/
public function characters(): HasMany
{
return $this->hasMany(Character::class);
}
/**
* Get the notifications for this user.
*/
public function notifications(): HasMany
{
return $this->hasMany(Notification::class)->orderBy('created_at', 'desc');
}
}