first commit
This commit is contained in:
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user