Files
thecrimesynticate/app/Services/Game/CrimeService.php
T
Doar MihaiandClaude Sonnet 4.6 3e6a84503d fix: address final review findings — nerve consume ordering, character init, seeder costs, dead column
- Consume nerve before energy in CrimeService so energy is never lost when nerve is insufficient
- Initialize nerve/max_nerve from config in CharacterController::store (same pattern as energy/health)
- Add nerve_cost to all CrimeSeeder entries proportional to difficulty (1-5 range)
- Remove nerve_regen_at from Character $fillable/$casts/@property — column exists but is not yet wired up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 17:09:21 +03:00

192 lines
5.7 KiB
PHP

<?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;
use App\Services\Game\NerveService;
class CrimeService
{
protected EnergyService $energyService;
protected EconomyService $economyService;
protected NerveService $nerveService;
public function __construct()
{
$this->energyService = new EnergyService();
$this->economyService = new EconomyService();
$this->nerveService = new NerveService();
}
/**
* 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 nerve first so energy is never deducted if nerve is insufficient
if (!$this->nerveService->consume($character, $crime->nerve_cost)) {
throw new \Exception('Not enough nerve to attempt this crime.');
}
// 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,
'nerve_cost' => $crime->nerve_cost,
'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('attempted_at')
->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('attempted_at')
->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('attempted_at')
->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();
}
}