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' )); } }