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