;;; Combat systems implemented here (defvar *combat-menu-options-display* " Actions: 1 | Attack | a 2 | Regen. Shields | r 3 | Overcharge Gun | o 4 | Attempt to flee | f ") ;; Bizarre Core Assumptions TODO? ;; * Whenever we think of engaging an enemy, the actual enemy object ;; is always whatever the first element of the (enemy-ships *sector*) ;; list. This is done because I'm lazy and it's fairly easy, will ;; figure out a different way if/when it's needed, but in essence, ;; the player typically does not make a choice about the characteristics ;; of the enemy they fight, the game chooses for you. If this is true, ;; this method is fine. ;; Combat options: ;; Attack (with specific weapon) ;; Regen shields ;; - (add value to rep-shield-val at cost of overcharing ;; - (overcharing can cause warp field to go into low-power) ;; - (overcharging can cause hull damage) ;; Overcharge gun at cost of overcharing reactor ;; Attempt to flee into the warp ;; - (requires full power / overcharged reactor) ;; - (can also cause reactor to go low power, which removes ability to regen/overcharge) ;; TODO - Combat always pops off first enemy from the enemy-ships list (defun combat-loop (sector) "The main loop responsbile for resolving combat situations" (let ((combat-state T)) (loop while combat-state do (progn (format T *combat-menu-options-display*) (let ((top-level-selection (prompt-read "Enter a selection: "))) (case (read-from-string top-level-selection) (1 (progn ;; There is a way to collapse this progn into a single ;; function call, but it's arguably a lot less readable (let* ((weapon (weapon-select-menu (player-ship-obj sector))) (attack-result (player-ship-attack (player-ship-obj sector) (first (enemy-ships sector)) weapon))) (if attack-result (progn (setf (enemy-ships sector) (delete attack-result (enemy-ships sector))) (format T "Your opponent has been vanquished!~%") (top-level-game-menu)))) (enemy-turn sector))))))))) (defun enemy-turn (sector) "Perform AI actions based on AI-type associated with enemy ship" (case (ai-type (first (enemy-ships sector))) (aggressive (progn ;; This AI will just stupidly attack until it's out of ammo ;; If it runs out of ammo, it will try and flee )) (fearful ()) (unpredicatable ()))) ;; Select weapon ;; Determine if hit (% chance?) ;; If hit resolve shield ;; -- IF Shields down (ie shield damage causes target shield value to be 0) ;; -- Apply hull damage ;; ELSE ;; -- Decrement shield value per value of shield dmg (defun weapon-select-menu (player-ship-obj) (let ((weapon-list (loop for weapon in (weapons player-ship-obj) for i from 0 collect (list i (name weapon) (shield-dmg weapon) (hull-dmg weapon) (ammo-cost weapon))))) (format T "~%Available Ammo: ~A~%~%" (ammo (inventory player-ship-obj))) (format T "Available Weapons: ~%") (format-table T weapon-list :column-label '("Number" "Name" "Shield Damage" "Hull Damage" "Ammo Cost")) (let ((selection (prompt-read "Select a weapon to via the number column: "))) (format t "Selection was number: ~A and weapon name: ~A~%~%" selection (second (nth (parse-integer selection) weapon-list))) (return-from weapon-select-menu (nth (parse-integer selection) (weapons player-ship-obj)))))) (defun calculate-shield-damage (attacker-obj target-obj attacker-weapon) "Given a target and a weapon, return a value that the targets shield value should now be set as" (let ((new-shield-value (- (rep-shield-val target-obj) (shield-dmg attacker-weapon)))) (setf (ammo (inventory attacker-obj)) (- (ammo (inventory attacker-obj)) (ammo-cost attacker-weapon))) (if (>= 0 new-shield-value) (return-from calculate-shield-damage 0) (return-from calculate-shield-damage new-shield-value)))) (defun calculate-hull-damage (attacker-obj target-obj attacker-weapon) "Given a target and a weapon, return a value that the targets hull value should now be set as" (let ((new-hull-value (- (armor-val target-obj) (hull-dmg attacker-weapon)))) (setf (ammo (inventory attacker-obj)) (- (ammo (inventory attacker-obj)) (ammo-cost attacker-weapon))) (if (>= 0 new-hull-value) (return-from calculate-hull-damage 0) (return-from calculate-hull-damage new-hull-value)))) (defun player-ship-attack (player-ship-obj target-obj attacker-weapon) "Given a target and the attackers chosen weapon resolve the combat by calculating shield/hull damage and resolving resources. If the target is reduced to 0 hull points, return the target-obj and end combat" (if (> (ammo-cost attacker-weapon) (ammo (inventory player-ship-obj))) (progn (format T "You don't have enough ammo to fire your ~A gun!~%" (name attacker-weapon)) (return-from player-ship-attack NIL))) (let ((calculated-shield-damage (calculate-shield-damage player-ship-obj target-obj attacker-weapon)) (calculated-hull-damage (calculate-hull-damage player-ship-obj target-obj attacker-weapon))) (setf (rep-shield-val target-obj) calculated-shield-damage) ;; Resolve shield hit (if (> (rep-shield-val target-obj) 0) (format T "You hit their repulsor shields for ~A !~%" (shield-dmg attacker-weapon))) (if (= 0 calculated-shield-damage) (progn (format T "Their shields are down!~%") (setf (armor-val target-obj) calculated-hull-damage) (format T "You hit their hull for ~A !~%" (hull-dmg attacker-weapon)))) (if (= 0 calculated-hull-damage) (progn (format T "Their hulls been breached! You've won the exchange!~%") (return-from player-ship-attack target-obj)) NIL))) (defun display-weapons (player-ship-obj) "Given player ship, display weapons and associated attributes" (let* ((weapons (weapons player-ship-obj)) (weapon-list (loop for weapon in weapons collect (list (name weapon) (shield-dmg weapon) (hull-dmg weapon) (ammo-cost weapon))))) (format T "~%WEAPON DETAILS~%") (format-table T weapon-list :column-label '("Name" "Shield Damage" "Hull Damage" "Ammo Cost"))))