Files
thecrimesynticate/app/Http/Controllers/Game/AttackController.php
T

331 lines
12 KiB
PHP
Raw Normal View History

2026-07-04 17:09:55 +03:00
<?php
namespace App\Http\Controllers\Game;
use App\Http\Controllers\Controller;
use App\Models\Character;
use App\Models\Attack as AttackModel;
use App\Services\Game\CombatService;
use App\Services\Game\HealthService;
use App\Services\Game\JailService;
use App\Services\Game\NotificationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AttackController extends Controller
{
protected CombatService $combatService;
protected HealthService $healthService;
protected JailService $jailService;
protected NotificationService $notificationService;
public function __construct(
CombatService $combatService,
HealthService $healthService,
JailService $jailService,
NotificationService $notificationService
) {
$this->combatService = $combatService;
$this->healthService = $healthService;
$this->jailService = $jailService;
$this->notificationService = $notificationService;
}
/**
* Display the attack page with potential targets
*/
public function index(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
// Check if character is in jail or hospital
if ($character->isInJail() || $character->isInHospital() || !$character->canAct()) {
return redirect()->route('game.dashboard')
->with('error', 'You cannot attack while in jail, hospital, or unable to act.');
}
// Get potential targets (other characters in same location, not in jail/hospital)
$potentialTargets = Character::where('location_id', $character->location_id)
->where('id', '!=', $character->id)
->where('status', 'active')
->where('health', '>', 0)
->orderBy('level', 'desc')
->get();
// Get attack cooldown
$cooldownRemaining = $this->combatService->getAttackCooldownRemaining($character);
return view('game.attack.index', compact(
'character',
'potentialTargets',
'cooldownRemaining'
));
}
/**
* Show attack preview for a specific target
*/
public function preview(Request $request, Character $target): View
{
$attacker = $request->user()->character;
if (!$attacker) {
return redirect()->route('game.character.create');
}
// Check if can attack this target
if (!$this->combatService->canAttack($attacker, $target)) {
return redirect()->route('game.attack.index')
->with('error', 'You cannot attack this target right now.');
}
// Calculate preview stats
$winProbability = $this->combatService->getWinProbability($attacker, $target);
$hitChance = $this->combatService->getHitChance($attacker, $target);
$attackerPower = $attacker->getAttackPowerAttribute();
$defenderDefense = $target->getDefenseAttribute();
$cooldownRemaining = $this->combatService->getAttackCooldownRemaining($attacker);
// Check for weapon and armor
$attackerWeapon = $attacker->inventory()
->where('equipped', true)
->whereHas('item', function($q) {
$q->where('type', 'weapon');
})
->first();
$defenderArmor = $target->inventory()
->where('equipped', true)
->whereHas('item', function($q) {
$q->where('type', 'armor');
})
->first();
return view('game.attack.preview', compact(
'attacker',
'target',
'winProbability',
'hitChance',
'attackerPower',
'defenderDefense',
'cooldownRemaining',
'attackerWeapon',
'defenderArmor'
));
}
/**
* Process an attack on another character
*/
public function attack(Request $request, Character $target): RedirectResponse
{
$attacker = $request->user()->character;
if (!$attacker) {
return redirect()->route('game.character.create');
}
$isMugging = $request->has('mug') && $request->boolean('mug');
// Check if can attack this target
if (!$this->combatService->canAttack($attacker, $target)) {
$cooldown = $this->combatService->getAttackCooldownRemaining($attacker);
if ($cooldown > 0) {
return redirect()->route('game.attack.index')
->with('error', "You must wait {$cooldown} seconds before attacking again.");
}
return redirect()->route('game.attack.index')
->with('error', 'You cannot attack this target right now.');
}
try {
// Process the attack
$result = $this->combatService->processAttack($attacker, $target, $isMugging);
// Record the attack
$attackRecord = AttackModel::create([
'attacker_id' => $attacker->id,
'defender_id' => $target->id,
'attacker_damage' => $result['attacker_damage'],
'defender_damage' => $result['defender_damage'],
'attacker_health_after' => $attacker->health,
'defender_health_after' => $target->health,
'result' => $result['result']->value,
'money_stolen' => $result['money_stolen'],
'experience_gained' => $result['experience_gained'],
'was_critical' => $result['was_critical'],
'was_mugging' => $result['was_mugging'],
'attacker_weapon_id' => $attacker->inventory()
->where('equipped', true)
->whereHas('item', function($q) { $q->where('type', 'weapon'); })
->first()?->item_id,
'defender_armor_id' => $target->inventory()
->where('equipped', true)
->whereHas('item', function($q) { $q->where('type', 'armor'); })
->first()?->item_id,
'attacked_at' => now(),
]);
// Handle death consequences
if ($result['defender_died']) {
// Defender died - send to hospital
$this->healthService->sendToHospital($target);
// If not mugging, send attacker to jail for killing
if (!$isMugging) {
$sentenceLength = config('game.jail.sentence_length', 3600);
$this->jailService->sendToJail($attacker, $sentenceLength);
}
}
if ($result['attacker_died']) {
// Attacker died - send to hospital
$this->healthService->sendToHospital($attacker);
}
// Send notifications
if ($result['result']->value === 'win') {
if ($isMugging) {
$this->notificationService->send($request->user(), 'mug_success', [
'target_name' => $target->name,
'money_stolen' => $result['money_stolen'],
]);
} else {
$this->notificationService->send($request->user(), 'attack_win', [
'target_name' => $target->name,
'experience_gained' => $result['experience_gained'],
]);
}
} else {
$this->notificationService->send($request->user(), 'attack_result', [
'target_name' => $target->name,
'result' => $result['result']->value,
]);
}
// Send notification to defender
if ($target->user) {
$this->notificationService->send($target->user, 'attacked', [
'attacker_name' => $attacker->name,
'result' => $result['result']->value,
'damage_taken' => $result['attacker_damage'],
]);
}
$successMessage = match ($result['result']->value) {
'win' => $isMugging ? "You mugged {$target->name} and stole $" . number_format($result['money_stolen']) . "!" : "You defeated {$target->name}!",
'lose' => "You lost to {$target->name}!",
'draw' => "Your attack against {$target->name} ended in a draw.",
default => "Attack result: {$result['result']->label()}",
};
return redirect()->route('game.attack.index')
->with('success', $successMessage);
} catch (\Exception $e) {
return redirect()->route('game.attack.index')
->with('error', $e->getMessage());
}
}
/**
* Place a hit/bounty on a character
*/
public function placeHit(Request $request, Character $target): RedirectResponse
{
$attacker = $request->user()->character;
if (!$attacker) {
return redirect()->route('game.character.create');
}
$validated = $request->validate([
'amount' => ['required', 'integer', 'min:1000'],
'reason' => ['nullable', 'string', 'max:255'],
]);
$hitCost = config('game.attack.hit_list_cost', 5000);
if (!$attacker->hasMoney($validated['amount'] + $hitCost)) {
return redirect()->back()
->with('error', 'You need at least $' . number_format($validated['amount'] + $hitCost) . ' to place a hit.');
}
// Place the hit - TODO: Implement Hit model
$attacker->removeMoney($validated['amount'] + $hitCost);
$this->notificationService->send($request->user(), 'hit_placed', [
'target_name' => $target->name,
'amount' => $validated['amount'],
]);
return redirect()->route('game.attack.index')
->with('success', "Hit placed on {$target->name} for $" . number_format($validated['amount']) . "!");
}
/**
* Get recent attacks for a character
*/
public function history(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
// Attacks made by this character
$myAttacks = AttackModel::where('attacker_id', $character->id)
->with(['defender', 'defender.user'])
->latest('attacked_at')
->paginate(20);
// Attacks made against this character
$attacksOnMe = AttackModel::where('defender_id', $character->id)
->with(['attacker', 'attacker.user'])
->latest('attacked_at')
->paginate(20);
return view('game.attack.history', compact(
'character',
'myAttacks',
'attacksOnMe'
));
}
/**
* Get characters who have attacked you recently (for revenge)
*/
public function revengeList(Request $request): View
{
$character = $request->user()->character;
if (!$character) {
return redirect()->route('game.character.create');
}
// Get characters who attacked this character and are still alive
$revengeTargets = AttackModel::where('defender_id', $character->id)
->where('attacker_id', '!=', $character->id)
->whereHas('attacker', function($q) {
$q->where('health', '>', 0)
->where('status', 'active');
})
->with(['attacker', 'attacker.user'])
->latest('attacked_at')
->get()
->unique('attacker_id')
->pluck('attacker');
return view('game.attack.revenge', compact(
'character',
'revengeTargets'
));
}
}