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
@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands\Game;
use App\Models\Character;
use App\Services\Game\EnergyService;
use Illuminate\Console\Command;
class ProcessEnergyRegen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'game:regen-energy';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate energy for all characters';
/**
* Execute the console command.
*/
public function handle(EnergyService $energyService): void
{
$this->info('Starting energy regeneration...');
$characters = Character::where('energy', '<', \DB::raw('max_energy'))
->where('status', 'active')
->get();
$count = $characters->count();
$this->info("Regenerating energy for {$count} characters...");
foreach ($characters as $character) {
$energyService->regenerate($character);
}
$this->info('Energy regeneration completed!');
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands\Game;
use App\Models\Character;
use App\Services\Game\HealthService;
use Illuminate\Console\Command;
class ProcessHealthRegen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'game:regen-health';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate health for all characters';
/**
* Execute the console command.
*/
public function handle(HealthService $healthService): void
{
$this->info('Starting health regeneration...');
$characters = Character::where('health', '<', \DB::raw('max_health'))
->where('status', 'active')
->where('health', '>', 0)
->get();
$count = $characters->count();
$this->info("Regenerating health for {$count} characters...");
foreach ($characters as $character) {
$healthService->regenerate($character);
}
$this->info('Health regeneration completed!');
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Enums;
enum AttackResult: string
{
case WIN = 'win';
case LOSE = 'lose';
case DRAW = 'draw';
case ESCAPE = 'escape';
/**
* Get all result values as an array
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Get the display name for a result
*/
public function label(): string
{
return match($this) {
self::WIN => 'Victory',
self::LOSE => 'Defeat',
self::DRAW => 'Draw',
self::ESCAPE => 'Escaped',
};
}
/**
* Check if the attacker won
*/
public function attackerWon(): bool
{
return $this === self::WIN;
}
/**
* Check if the defender won (attacker lost)
*/
public function defenderWon(): bool
{
return $this === self::LOSE;
}
/**
* Get the experience reward for this result (for attacker)
*/
public function experienceReward(): int
{
return match($this) {
self::WIN => 200,
self::LOSE => 50,
self::DRAW => 100,
self::ESCAPE => 25,
};
}
/**
* Get the money reward for this result (for attacker)
*/
public function moneyReward(): int
{
return match($this) {
self::WIN => 500,
self::LOSE => 0,
self::DRAW => 100,
self::ESCAPE => 0,
};
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Enums;
enum CharacterStatus: string
{
case ACTIVE = 'active';
case IN_HOSPITAL = 'in_hospital';
case IN_JAIL = 'in_jail';
case DEAD = 'dead';
/**
* Get all status values as an array
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Get the display name for a status
*/
public function label(): string
{
return match($this) {
self::ACTIVE => 'Active',
self::IN_HOSPITAL => 'In Hospital',
self::IN_JAIL => 'In Jail',
self::DEAD => 'Dead',
};
}
/**
* Check if character can perform actions
*/
public function canAct(): bool
{
return $this === self::ACTIVE;
}
/**
* Get the color class for the status badge
*/
public function color(): string
{
return match($this) {
self::ACTIVE => 'bg-green-500',
self::IN_HOSPITAL => 'bg-red-500',
self::IN_JAIL => 'bg-yellow-500',
self::DEAD => 'bg-gray-500',
};
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace App\Enums;
enum CrimeDifficulty: string
{
case EASY = 'easy';
case MEDIUM = 'medium';
case HARD = 'hard';
case VERY_HARD = 'very_hard';
/**
* Get all difficulty values as an array
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Get the display name for a difficulty
*/
public function label(): string
{
return match($this) {
self::EASY => 'Easy',
self::MEDIUM => 'Medium',
self::HARD => 'Hard',
self::VERY_HARD => 'Very Hard',
};
}
/**
* Get the success rate modifier for this difficulty
*/
public function successModifier(): float
{
return match($this) {
self::EASY => 0.20, // +20% success rate
self::MEDIUM => 0.00, // Base success rate
self::HARD => -0.20, // -20% success rate
self::VERY_HARD => -0.40,// -40% success rate
};
}
/**
* Get the reward multiplier for this difficulty
*/
public function rewardMultiplier(): float
{
return match($this) {
self::EASY => 0.5,
self::MEDIUM => 1.0,
self::HARD => 2.0,
self::VERY_HARD => 3.0,
};
}
/**
* Get the color class for the difficulty badge
*/
public function color(): string
{
return match($this) {
self::EASY => 'bg-green-500',
self::MEDIUM => 'bg-yellow-500',
self::HARD => 'bg-orange-500',
self::VERY_HARD => 'bg-red-500',
};
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Enums;
enum ItemType: string
{
case WEAPON = 'weapon';
case ARMOR = 'armor';
case CONSUMABLE = 'consumable';
case MISC = 'misc';
case CURRENCY = 'currency';
/**
* Get all item type values as an array
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Get the display name for an item type
*/
public function label(): string
{
return match($this) {
self::WEAPON => 'Weapon',
self::ARMOR => 'Armor',
self::CONSUMABLE => 'Consumable',
self::MISC => 'Miscellaneous',
self::CURRENCY => 'Currency',
};
}
/**
* Check if this item type can be equipped
*/
public function canEquip(): bool
{
return in_array($this, [self::WEAPON, self::ARMOR]);
}
/**
* Check if this item type can be used/consumed
*/
public function canUse(): bool
{
return $this === self::CONSUMABLE;
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): View
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*/
public function show(): View
{
return view('auth.confirm-password');
}
/**
* Confirm the user's password.
*/
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(route('dashboard', absolute: false));
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*/
public function __invoke(Request $request): RedirectResponse|View
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: view('auth.verify-email');
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @throws ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class PasswordController extends Controller
{
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back()->with('status', 'password-updated');
}
}
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): View
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @throws ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @throws ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(route('dashboard', absolute: false));
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
@@ -0,0 +1,204 @@
<?php
namespace App\Http\Controllers\Game;
use App\Http\Controllers\Controller;
use App\Models\Character;
use App\Models\Location;
use App\Models\ActivityLog;
use App\Services\Game\EconomyService;
use App\Services\Game\EnergyService;
use App\Services\Game\HealthService;
use App\Services\Game\NotificationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class CharacterController extends Controller
{
protected EconomyService $economyService;
protected EnergyService $energyService;
protected HealthService $healthService;
protected NotificationService $notificationService;
public function __construct()
{
$this->economyService = new EconomyService();
$this->energyService = new EnergyService();
$this->healthService = new HealthService();
$this->notificationService = new NotificationService();
}
/**
* Show the character creation form
*/
public function create(Request $request): View
{
$genders = ['male', 'female', 'other'];
$locations = Location::orderBy('name')->get();
return view('game.character.create', compact('genders', 'locations'));
}
/**
* Store a newly created character
*/
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:50', 'unique:characters,name'],
'gender' => ['required', 'string', 'in:male,female,other'],
'location_id' => ['required', 'exists:locations,id'],
]);
$user = Auth::user();
// Check if user already has a character
if ($user->character) {
return redirect()->route('game.dashboard')
->with('error', 'You already have a character!');
}
// Create the character
$character = Character::create([
'user_id' => $user->id,
'name' => $validated['name'],
'gender' => $validated['gender'],
'location_id' => $validated['location_id'],
'level' => 1,
'experience' => 0,
'money' => $this->economyService->getStartingMoney(),
'bank' => $this->economyService->getStartingBank(),
'energy' => config('game.energy.start_amount', 100),
'max_energy' => config('game.energy.max', 100),
'health' => config('game.health.start_amount', 100),
'max_health' => config('game.health.max', 100),
'strength' => 5,
'defense' => 5,
'speed' => 5,
'dexterity' => 5,
'status' => 'active',
'last_action_at' => now(),
]);
// Log character creation
ActivityLog::logCharacterCreated($character);
return redirect()->route('game.dashboard')
->with('success', 'Character created successfully! Welcome to the game.');
}
/**
* Display the character profile
*/
public function show(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
return view('game.character.show', compact('character'));
}
/**
* Show the character edit form
*/
public function edit(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
return view('game.character.edit', compact('character'));
}
/**
* Update the character
*/
public function update(Request $request): RedirectResponse
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
$validated = $request->validate([
'name' => ['sometimes', 'string', 'max:50', 'unique:characters,name,'.$character->id],
'gender' => ['sometimes', 'string', 'in:male,female,other'],
]);
$character->update($validated);
return redirect()->route('game.character.show')
->with('success', 'Character updated successfully!');
}
/**
* Deposit money to bank
*/
public function deposit(Request $request): RedirectResponse
{
$character = $request->user()->character;
$validated = $request->validate([
'amount' => ['required', 'integer', 'min:1'],
]);
if ($this->economyService->depositToBank($character, $validated['amount'])) {
return redirect()->back()
->with('success', 'Money deposited to bank successfully!');
}
return redirect()->back()
->with('error', 'Failed to deposit money. You may not have enough money or reached the bank limit.');
}
/**
* Withdraw money from bank
*/
public function withdraw(Request $request): RedirectResponse
{
$character = $request->user()->character;
$validated = $request->validate([
'amount' => ['required', 'integer', 'min:1'],
]);
if ($this->economyService->withdrawFromBank($character, $validated['amount'])) {
return redirect()->back()
->with('success', 'Money withdrawn from bank successfully!');
}
return redirect()->back()
->with('error', 'Failed to withdraw money. You may not have enough in your bank.');
}
/**
* Revive character from hospital
*/
public function revive(Request $request): RedirectResponse
{
$character = $request->user()->character;
if (!$character->isDead() && !$character->isInHospital()) {
return redirect()->route('game.dashboard')
->with('error', 'Your character is not in hospital.');
}
if ($this->healthService->revive($character)) {
$this->notificationService->sendCharacterRevived($request->user(), $character);
return redirect()->route('game.dashboard')
->with('success', 'Character revived successfully!');
}
return redirect()->route('game.dashboard')
->with('error', 'Failed to revive character. You may not have enough money.');
}
}
@@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\Game;
use App\Http\Controllers\Controller;
use App\Models\Crime;
use App\Models\CrimeAttempt;
use App\Services\Game\CrimeService;
use App\Services\Game\EnergyService;
use App\Services\Game\NotificationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CrimeController extends Controller
{
protected CrimeService $crimeService;
protected EnergyService $energyService;
protected NotificationService $notificationService;
public function __construct()
{
$this->crimeService = new CrimeService();
$this->energyService = new EnergyService();
$this->notificationService = new NotificationService();
}
/**
* Display a listing of available crimes
*/
public function index(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
$availableCrimes = $this->crimeService->getAvailableCrimes($character);
$recentAttempts = $this->crimeService->getRecentAttempts($character, 5);
return view('game.crimes.index', compact('character', 'availableCrimes', 'recentAttempts'));
}
/**
* Attempt to commit a crime
*/
public function attempt(Request $request, Crime $crime): RedirectResponse
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
try {
$attempt = $this->crimeService->attemptCrime($character, $crime);
if ($attempt->isSuccessful()) {
$this->notificationService->sendCrimeSuccess($request->user(), $crime, $attempt->reward);
return redirect()->route('game.crimes.index')
->with('success', 'Crime successful! You earned $'.$attempt->reward);
} else {
$this->notificationService->sendCrimeFailure($request->user(), $crime);
return redirect()->route('game.crimes.index')
->with('error', 'Crime failed! You took damage.');
}
} catch (\Exception $e) {
return redirect()->route('game.crimes.index')
->with('error', $e->getMessage());
}
}
/**
* Show crime details
*/
public function show(Request $request, Crime $crime): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
// Check if crime is on cooldown
$onCooldown = $this->crimeService->isOnCooldown($character, $crime);
$cooldownRemaining = $onCooldown ? $this->crimeService->getCooldownRemaining($character, $crime) : 0;
// Calculate success rate for this character
$successRate = $crime->calculateSuccessRate($character);
return view('game.crimes.show', compact('character', 'crime', 'onCooldown', 'cooldownRemaining', 'successRate'));
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Game;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\Notification;
use Illuminate\Http\Request;
use Illuminate\View\View;
class DashboardController extends Controller
{
/**
* Display the game dashboard
*/
public function index(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
// Get recent activity logs
$recentActivity = ActivityLog::where('character_id', $character->id)
->latest()
->take(10)
->get();
// Get unread notifications
$unreadNotifications = $request->user()->notifications()
->whereNull('read_at')
->orderBy('created_at', 'desc')
->get();
return view('game.dashboard', compact('character', 'recentActivity', 'unreadNotifications'));
}
}
@@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers\Game;
use App\Http\Controllers\Controller;
use App\Models\Inventory;
use App\Models\Item;
use App\Services\Game\EconomyService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class InventoryController extends Controller
{
protected EconomyService $economyService;
public function __construct()
{
$this->economyService = new EconomyService();
}
/**
* Display the character's inventory
*/
public function index(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
$inventory = $character->inventory()->with('item')->get();
return view('game.inventory.index', compact('character', 'inventory'));
}
/**
* Equip an item
*/
public function equip(Request $request, Inventory $inventory): RedirectResponse
{
$character = $request->user()->character;
if (!$character || $inventory->character_id !== $character->id) {
return redirect()->route('game.inventory.index')
->with('error', 'Unauthorized action.');
}
if ($inventory->equip()) {
return redirect()->route('game.inventory.index')
->with('success', 'Item equipped successfully!');
}
return redirect()->route('game.inventory.index')
->with('error', 'Cannot equip this item.');
}
/**
* Unequip an item
*/
public function unequip(Request $request, Inventory $inventory): RedirectResponse
{
$character = $request->user()->character;
if (!$character || $inventory->character_id !== $character->id) {
return redirect()->route('game.inventory.index')
->with('error', 'Unauthorized action.');
}
$inventory->unequip();
return redirect()->route('game.inventory.index')
->with('success', 'Item unequipped successfully!');
}
/**
* Use an item
*/
public function use(Request $request, Inventory $inventory): RedirectResponse
{
$character = $request->user()->character;
if (!$character || $inventory->character_id !== $character->id) {
return redirect()->route('game.inventory.index')
->with('error', 'Unauthorized action.');
}
if ($inventory->use($character)) {
return redirect()->route('game.inventory.index')
->with('success', 'Item used successfully!');
}
return redirect()->route('game.inventory.index')
->with('error', 'Cannot use this item.');
}
/**
* Drop an item
*/
public function drop(Request $request, Inventory $inventory): RedirectResponse
{
$character = $request->user()->character;
if (!$character || $inventory->character_id !== $character->id) {
return redirect()->route('game.inventory.index')
->with('error', 'Unauthorized action.');
}
$validated = $request->validate([
'quantity' => ['required', 'integer', 'min:1', 'max:'.$inventory->quantity],
]);
if ($inventory->remove($validated['quantity'])) {
return redirect()->route('game.inventory.index')
->with('success', 'Item dropped successfully!');
}
return redirect()->route('game.inventory.index')
->with('error', 'Failed to drop item.');
}
}
@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Middleware\Game;
use App\Services\Game\EnergyService;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckEnergy
{
protected EnergyService $energyService;
public function __construct(EnergyService $energyService)
{
$this->energyService = $energyService;
}
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $action = 'default'): Response
{
$character = $request->user()->character;
// Check if character can act
if (!$character->canAct()) {
return redirect()->route('game.dashboard')
->with('error', 'You cannot perform actions right now.');
}
// Get energy cost for this action type
$cost = $this->energyService->getCostFor($action);
// Check if character has enough energy
if (!$character->hasEnergy($cost)) {
return redirect()->route('game.dashboard')
->with('error', "You don't have enough energy ({$cost} required) for this action.");
}
return $next($request);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware\Game;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckInHospital
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$character = $request->user()->character;
if ($character->isInHospital() || $character->isDead()) {
$releaseTime = $character->in_hospital_until ? $character->in_hospital_until->diffForHumans() : 'unknown';
$reviveCost = config('game.health.hospital_cost', 1000);
return redirect()->route('game.hospital')
->with('error', "You are in hospital and cannot perform actions. You will be released {$releaseTime} or can revive for \${$reviveCost}.");
}
return $next($request);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware\Game;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckInJail
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$character = $request->user()->character;
if ($character->isInJail()) {
$releaseTime = $character->in_jail_until ? $character->in_jail_until->diffForHumans() : 'unknown';
return redirect()->route('game.jail')
->with('error', "You are in jail and cannot perform actions. You will be released {$releaseTime}.");
}
return $next($request);
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware\Game;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class VerifyCharacter
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// Check if user is authenticated
if (!$request->user()) {
return redirect()->route('login');
}
// Check if user has a character
$character = $request->user()->character;
if (!$character) {
// No character exists, redirect to character creation
return redirect()->route('game.character.create');
}
// Character exists, proceed with request
return $next($request);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws ValidationException
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @throws ValidationException
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}
+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');
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class GameServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
// Register game services as singletons
$this->app->singleton(\App\Services\Game\EnergyService::class, function ($app) {
return new \App\Services\Game\EnergyService();
});
$this->app->singleton(\App\Services\Game\HealthService::class, function ($app) {
return new \App\Services\Game\HealthService();
});
$this->app->singleton(\App\Services\Game\EconomyService::class, function ($app) {
return new \App\Services\Game\EconomyService();
});
$this->app->singleton(\App\Services\Game\CrimeService::class, function ($app) {
return new \App\Services\Game\CrimeService();
});
$this->app->singleton(\App\Services\Game\NotificationService::class, function ($app) {
return new \App\Services\Game\NotificationService();
});
}
/**
* Bootstrap services.
*/
public function boot(): void
{
// Boot logic if needed
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;
class ScheduleServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
$this->commands([
\App\Console\Commands\Game\ProcessEnergyRegen::class,
\App\Console\Commands\Game\ProcessHealthRegen::class,
]);
}
}
+182
View File
@@ -0,0 +1,182 @@
<?php
namespace App\Services\Game;
use App\Models\Character;
use App\Models\Crime;
use App\Models\CrimeAttempt;
use App\Models\ActivityLog;
use App\Enums\CharacterStatus;
class CrimeService
{
protected EnergyService $energyService;
protected EconomyService $economyService;
public function __construct()
{
$this->energyService = new EnergyService();
$this->economyService = new EconomyService();
}
/**
* Attempt a crime
*/
public function attemptCrime(Character $character, Crime $crime): CrimeAttempt
{
// Check if character can attempt this crime
if (!$crime->canBeAttemptedBy($character)) {
throw new \Exception('Character cannot attempt this crime.');
}
// Check if character can act
if (!$character->canAct()) {
throw new \Exception('Character cannot perform actions right now.');
}
// Consume energy
if (!$this->energyService->consume($character, $crime->energy_cost, 'crime')) {
throw new \Exception('Not enough energy to attempt this crime.');
}
// Calculate success rate
$successRate = $crime->calculateSuccessRate($character);
$isSuccess = random_int(0, 9999) / 10000 <= $successRate;
$reward = 0;
$experience = 0;
if ($isSuccess) {
// Successful crime
$reward = $crime->calculateReward($character);
$experience = $crime->calculateExperience($character);
$this->economyService->addMoney($character, $reward, "Crime: {$crime->name}");
$character->addExperience($experience);
// Log success
ActivityLog::logCrimeAttempt($character, $crime, true, $reward);
} else {
// Failed crime - take health penalty
$healthPenalty = (int) ($character->max_health * config('game.crime.failure_penalty', 0.10));
$character->takeDamage($healthPenalty);
// If dead, send to hospital
if ($character->isDead()) {
$character->sendToHospital();
}
// Log failure
ActivityLog::logCrimeAttempt($character, $crime, false, 0);
}
// Create crime attempt record
$attempt = CrimeAttempt::create([
'character_id' => $character->id,
'crime_id' => $crime->id,
'success' => $isSuccess,
'reward' => $reward,
'experience_gained' => $experience,
'attempted_at' => now(),
]);
return $attempt;
}
/**
* Get available crimes for a character
*/
public function getAvailableCrimes(Character $character): \Illuminate\Database\Eloquent\Collection
{
return Crime::query()
->where(function($query) use ($character) {
$query->whereNull('requires_level')
->orWhere('requires_level', '<=', $character->level);
})
->where(function($query) use ($character) {
$query->whereNull('location_id')
->orWhere('location_id', $character->location_id);
})
->where('energy_cost', '<=', $character->energy)
->orderBy('difficulty')
->orderBy('name')
->get();
}
/**
* Get recent crime attempts for a character
*/
public function getRecentAttempts(Character $character, int $limit = 10): \Illuminate\Database\Eloquent\Collection
{
return $character->crimeAttempts()
->with('crime')
->latest()
->take($limit)
->get();
}
/**
* Check if a crime is on cooldown for a character
*/
public function isOnCooldown(Character $character, Crime $crime): bool
{
$lastAttempt = $character->crimeAttempts()
->where('crime_id', $crime->id)
->latest()
->first();
if (!$lastAttempt) {
return false;
}
$cooldown = $crime->cooldown;
$timeSinceAttempt = now()->diffInSeconds($lastAttempt->attempted_at);
return $timeSinceAttempt < $cooldown;
}
/**
* Get remaining cooldown time for a crime
*/
public function getCooldownRemaining(Character $character, Crime $crime): int
{
if (!$this->isOnCooldown($character, $crime)) {
return 0;
}
$lastAttempt = $character->crimeAttempts()
->where('crime_id', $crime->id)
->latest()
->first();
$cooldown = $crime->cooldown;
$timeSinceAttempt = now()->diffInSeconds($lastAttempt->attempted_at);
return max(0, $cooldown - $timeSinceAttempt);
}
/**
* Create a new crime
*/
public function createCrime(array $data): Crime
{
return Crime::create($data);
}
/**
* Update a crime
*/
public function updateCrime(Crime $crime, array $data): Crime
{
$crime->update($data);
return $crime;
}
/**
* Delete a crime
*/
public function deleteCrime(Crime $crime): bool
{
return $crime->delete();
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace App\Services\Game;
use App\Models\Character;
class EconomyService
{
/**
* Transfer money between characters
*/
public function transfer(Character $from, Character $to, int $amount, string $reason = ''): bool
{
if (!$from->hasMoney($amount)) {
return false;
}
if ($from->id === $to->id) {
return false; // Can't transfer to self
}
$from->removeMoney($amount);
$to->addMoney($amount);
// Log the transaction
ActivityLog::logMoneyTransaction($from, 'spent', $amount, "Transfer to {$to->name}: {$reason}");
ActivityLog::logMoneyTransaction($to, 'earned', $amount, "Transfer from {$from->name}: {$reason}");
return true;
}
/**
* Add money to a character
*/
public function addMoney(Character $character, int $amount, string $reason = ''): void
{
$character->addMoney($amount);
if ($amount > 0 && $reason) {
ActivityLog::logMoneyTransaction($character, 'earned', $amount, $reason);
}
}
/**
* Remove money from a character
*/
public function removeMoney(Character $character, int $amount, string $reason = ''): bool
{
if (!$character->hasMoney($amount)) {
return false;
}
$character->removeMoney($amount);
if ($amount > 0 && $reason) {
ActivityLog::logMoneyTransaction($character, 'spent', $amount, $reason);
}
return true;
}
/**
* Deposit money to bank
*/
public function depositToBank(Character $character, int $amount): bool
{
return $character->depositToBank($amount);
}
/**
* Withdraw money from bank
*/
public function withdrawFromBank(Character $character, int $amount): bool
{
return $character->withdrawFromBank($amount);
}
/**
* Apply daily interest to all characters
*/
public function applyDailyInterest(): void
{
$characters = Character::all();
foreach ($characters as $character) {
$character->applyBankInterest();
}
}
/**
* Get the starting money for new characters
*/
public function getStartingMoney(): int
{
return config('game.economy.start_money', 5000);
}
/**
* Get the starting bank balance
*/
public function getStartingBank(): int
{
return config('game.economy.start_bank', 0);
}
/**
* Get the maximum bank balance
*/
public function getMaxBank(): int
{
return config('game.economy.max_bank', 10000000);
}
/**
* Get the bank interest rate
*/
public function getBankInterestRate(): float
{
return config('game.economy.bank_interest', 0.05);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace App\Services\Game;
use App\Models\Character;
class EnergyService
{
/**
* Consume energy for an action
*/
public function consume(Character $character, int $amount, string $action): bool
{
if (!$character->canAct()) {
return false;
}
if (!$character->hasEnergy($amount)) {
return false;
}
return $character->consumeEnergy($amount);
}
/**
* Regenerate energy for a character
*/
public function regenerate(Character $character): void
{
$regenRate = config('game.energy.regen_rate', 5);
$maxEnergy = config('game.energy.max', 100);
// Ensure character has max_energy set
if ($character->max_energy <= 0) {
$character->max_energy = $maxEnergy;
}
$character->addEnergy($regenRate);
}
/**
* Get the energy regen rate
*/
public function getRegenRate(): int
{
return config('game.energy.regen_rate', 5);
}
/**
* Get the energy regen interval in seconds
*/
public function getRegenInterval(): int
{
return config('game.energy.regen_interval', 300);
}
/**
* Check if character has enough energy for an action type
*/
public function hasEnoughFor(Character $character, string $actionType): bool
{
$cost = config("game.energy.action_costs.{$actionType}", config('game.energy.action_costs.default', 10));
return $character->hasEnergy($cost);
}
/**
* Get the energy cost for an action type
*/
public function getCostFor(string $actionType): int
{
return config("game.energy.action_costs.{$actionType}", config('game.energy.action_costs.default', 10));
}
/**
* Restore all energy for a character
*/
public function restoreAll(Character $character): void
{
$maxEnergy = config('game.energy.max', 100);
if ($character->max_energy <= 0) {
$character->max_energy = $maxEnergy;
}
$character->energy = $character->max_energy;
$character->save();
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace App\Services\Game;
use App\Models\Character;
class HealthService
{
/**
* Regenerate health for a character
*/
public function regenerate(Character $character): void
{
if ($character->isDead()) {
return;
}
$regenRate = config('game.health.regen_rate', 2);
$character->heal($regenRate);
}
/**
* Get the health regen rate
*/
public function getRegenRate(): int
{
return config('game.health.regen_rate', 2);
}
/**
* Get the health regen interval in seconds
*/
public function getRegenInterval(): int
{
return config('game.health.regen_interval', 600);
}
/**
* Heal a character
*/
public function heal(Character $character, int $amount): void
{
$character->heal($amount);
}
/**
* Deal damage to a character
*/
public function damage(Character $character, int $amount): int
{
return $character->takeDamage($amount);
}
/**
* Revive a character from hospital
*/
public function revive(Character $character): bool
{
if (!$character->isDead() && !$character->isInHospital()) {
return false;
}
$cost = config('game.health.hospital_cost', 1000);
if ($character->money < $cost) {
return false;
}
$character->money -= $cost;
$character->health = $character->max_health;
$character->status = 'active';
$character->in_hospital_until = null;
$character->save();
return true;
}
/**
* Send character to hospital
*/
public function sendToHospital(Character $character): void
{
$character->health = 0;
$character->status = 'in_hospital';
$character->in_hospital_until = now()->addSeconds(config('game.health.regen_interval', 600) * 5); // 5 intervals
$character->save();
}
/**
* Restore all health for a character
*/
public function restoreAll(Character $character): void
{
$character->health = $character->max_health;
if ($character->status !== 'active') {
$character->status = 'active';
}
$character->in_hospital_until = null;
$character->save();
}
/**
* Check if character needs hospital
*/
public function needsHospital(Character $character): bool
{
return $character->isDead() || $character->isInHospital();
}
}
+191
View File
@@ -0,0 +1,191 @@
<?php
namespace App\Services\Game;
use App\Models\User;
use App\Models\Notification;
use App\Models\Character;
use App\Models\Crime;
class NotificationService
{
/**
* Send a notification to a user
*/
public function send(User $user, string $type, array $data = [], bool $saveActivity = true): Notification
{
$notification = Notification::createForUser($user, $type, $data);
return $notification;
}
/**
* Send a system notification
*/
public function sendSystem(User $user, string $message): Notification
{
return Notification::sendSystem($user, $message);
}
/**
* Send a crime success notification
*/
public function sendCrimeSuccess(User $user, Crime $crime, int $reward): Notification
{
return Notification::sendCrimeSuccess($user, $crime, $reward);
}
/**
* Send a crime failure notification
*/
public function sendCrimeFailure(User $user, Crime $crime): Notification
{
return Notification::sendCrimeFailure($user, $crime);
}
/**
* Send a level up notification
*/
public function sendLevelUp(User $user, Character $character, int $newLevel): Notification
{
return $this->send($user, 'level_up', [
'character_name' => $character->name,
'level' => $newLevel,
]);
}
/**
* Send a money earned notification
*/
public function sendMoneyEarned(User $user, int $amount, string $reason): Notification
{
return $this->send($user, 'money_earned', [
'amount' => $amount,
'reason' => $reason,
]);
}
/**
* Send a money spent notification
*/
public function sendMoneySpent(User $user, int $amount, string $reason): Notification
{
return $this->send($user, 'money_spent', [
'amount' => $amount,
'reason' => $reason,
]);
}
/**
* Send an energy low notification
*/
public function sendEnergyLow(User $user, Character $character): Notification
{
return $this->send($user, 'energy_low', [
'character_name' => $character->name,
'energy' => $character->energy,
'max_energy' => $character->max_energy,
]);
}
/**
* Send a health low notification
*/
public function sendHealthLow(User $user, Character $character): Notification
{
return $this->send($user, 'health_low', [
'character_name' => $character->name,
'health' => $character->health,
'max_health' => $character->max_health,
]);
}
/**
* Send a character dead notification
*/
public function sendCharacterDead(User $user, Character $character): Notification
{
return $this->send($user, 'character_dead', [
'character_name' => $character->name,
'revive_cost' => config('game.health.hospital_cost', 1000),
]);
}
/**
* Send a character revived notification
*/
public function sendCharacterRevived(User $user, Character $character): Notification
{
return $this->send($user, 'character_revived', [
'character_name' => $character->name,
]);
}
/**
* Get unread notifications for a user
*/
public function getUnread(User $user): \Illuminate\Database\Eloquent\Collection
{
return $user->notifications()
->whereNull('read_at')
->orderBy('created_at', 'desc')
->get();
}
/**
* Get all notifications for a user
*/
public function getAll(User $user, int $limit = 20): \Illuminate\Database\Eloquent\Collection
{
return $user->notifications()
->orderBy('created_at', 'desc')
->take($limit)
->get();
}
/**
* Mark a notification as read
*/
public function markAsRead(Notification $notification): void
{
$notification->markAsRead();
}
/**
* Mark all notifications as read for a user
*/
public function markAllAsRead(User $user): int
{
return $user->notifications()
->whereNull('read_at')
->update(['read_at' => now()]);
}
/**
* Delete a notification
*/
public function delete(Notification $notification): bool
{
return $notification->delete();
}
/**
* Delete all read notifications for a user
*/
public function deleteAllRead(User $user): int
{
return $user->notifications()
->whereNotNull('read_at')
->delete();
}
/**
* Get the count of unread notifications for a user
*/
public function getUnreadCount(User $user): int
{
return $user->notifications()
->whereNull('read_at')
->count();
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.app');
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.guest');
}
}