diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 5e29634..84553f3 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -5,6 +5,8 @@ define-monster "Fury" description "Hades' messengers, torturers, assassins. Beware!" + health 10 + dexterity 10 strength 10 aggression 30 armour-class 3 @@ -12,6 +14,7 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" + type "whip" damage 2 define-item "Anaklusmos" @@ -20,6 +23,7 @@ define-weapon "Anaklusmos" description "Riptide, a sword for heroes!" + type "sword" damage 4 define-npc "Hades" diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 5e29634..84553f3 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -5,6 +5,8 @@ define-monster "Fury" description "Hades' messengers, torturers, assassins. Beware!" + health 10 + dexterity 10 strength 10 aggression 30 armour-class 3 @@ -12,6 +14,7 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" + type "whip" damage 2 define-item "Anaklusmos" @@ -20,6 +23,7 @@ define-weapon "Anaklusmos" description "Riptide, a sword for heroes!" + type "sword" damage 4 define-npc "Hades" diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index 6fbcee5..080bc7e 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -31,6 +31,7 @@ (defstruct monster (name "") (description "") + (health 0) (strength 0) (dexterity 0) (aggression 0) @@ -47,18 +48,16 @@ (defstruct weapon (name "") (description "") - (ranged NIL) + (type "generic") (damage 0)) (defun set-object-attribute (game-object property value) "Set the attribute 'property' of 'game-object' to 'value'" ;; Here follows Lisp magic :D (that took ages to get right...) - ;; [And half the magic is gone after being outsourced to build-symbol :-(] - ;; I'm not sure how elegant it is to call (eval) explicitly, but in this - ;; case I couldn't avoid it - I needed a mix between a macro and a function + ;; XXX It's not elegant to call (eval) explicitly, but in this case I can't + ;; find a way to avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -70,11 +69,11 @@ ;; Same comment applies as above (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) - ;; FIXME This is going to give problems with multiple values - ;; (but will that scenario ever take place?) + ;; FIXME This gives problems with multiple values (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) + ;; TODO set numbers to 0, strings to "" (setf (,command ,game-object) NIL))))) ;; XXX This function is probably superfluous, as all place objects are stored @@ -83,3 +82,8 @@ (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." (funcall list-function object-type place))) + +(defun monster-strikes-player (monster player) + "The monster attacks a player" + ;;TODO + ) diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 5e29634..84553f3 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -5,6 +5,8 @@ define-monster "Fury" description "Hades' messengers, torturers, assassins. Beware!" + health 10 + dexterity 10 strength 10 aggression 30 armour-class 3 @@ -12,6 +14,7 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" + type "whip" damage 2 define-item "Anaklusmos" @@ -20,6 +23,7 @@ define-weapon "Anaklusmos" description "Riptide, a sword for heroes!" + type "sword" damage 4 define-npc "Hades" diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index 6fbcee5..080bc7e 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -31,6 +31,7 @@ (defstruct monster (name "") (description "") + (health 0) (strength 0) (dexterity 0) (aggression 0) @@ -47,18 +48,16 @@ (defstruct weapon (name "") (description "") - (ranged NIL) + (type "generic") (damage 0)) (defun set-object-attribute (game-object property value) "Set the attribute 'property' of 'game-object' to 'value'" ;; Here follows Lisp magic :D (that took ages to get right...) - ;; [And half the magic is gone after being outsourced to build-symbol :-(] - ;; I'm not sure how elegant it is to call (eval) explicitly, but in this - ;; case I couldn't avoid it - I needed a mix between a macro and a function + ;; XXX It's not elegant to call (eval) explicitly, but in this case I can't + ;; find a way to avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -70,11 +69,11 @@ ;; Same comment applies as above (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) - ;; FIXME This is going to give problems with multiple values - ;; (but will that scenario ever take place?) + ;; FIXME This gives problems with multiple values (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) + ;; TODO set numbers to 0, strings to "" (setf (,command ,game-object) NIL))))) ;; XXX This function is probably superfluous, as all place objects are stored @@ -83,3 +82,8 @@ (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." (funcall list-function object-type place))) + +(defun monster-strikes-player (monster player) + "The monster attacks a player" + ;;TODO + ) diff --git a/lisp/player.lisp b/lisp/player.lisp index b96302c..0cbe51b 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -21,12 +21,17 @@ (money 0) (item NIL) (weapon "") + (armour-class 0) (place "") (experience 0) + (level 0) (max-health 50) (health 50) (game-admin NIL)) +;; How many XP are needed to level up? +;; XXX Make this configurable in ATL? +(defvar *level-experience* 100) (defstruct race (name "") @@ -50,3 +55,15 @@ (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." (funcall list-function object-type player))) + +(defun change-player-health (player amount) + "Change the player's health points" + (incf (player-health player) amount) + (when (> 1 (player-health player)) + (error "You died!"))) ;; TODO adjust this (especially for multiplayer) + +(defun add-player-experience (player amount) + "The player gains experience points" + (incf (player-experience player) amount) + (when (zerop (rem (player-experience player) *level-experience*)) + (incf (player-level player) player))) diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 5e29634..84553f3 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -5,6 +5,8 @@ define-monster "Fury" description "Hades' messengers, torturers, assassins. Beware!" + health 10 + dexterity 10 strength 10 aggression 30 armour-class 3 @@ -12,6 +14,7 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" + type "whip" damage 2 define-item "Anaklusmos" @@ -20,6 +23,7 @@ define-weapon "Anaklusmos" description "Riptide, a sword for heroes!" + type "sword" damage 4 define-npc "Hades" diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index 6fbcee5..080bc7e 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -31,6 +31,7 @@ (defstruct monster (name "") (description "") + (health 0) (strength 0) (dexterity 0) (aggression 0) @@ -47,18 +48,16 @@ (defstruct weapon (name "") (description "") - (ranged NIL) + (type "generic") (damage 0)) (defun set-object-attribute (game-object property value) "Set the attribute 'property' of 'game-object' to 'value'" ;; Here follows Lisp magic :D (that took ages to get right...) - ;; [And half the magic is gone after being outsourced to build-symbol :-(] - ;; I'm not sure how elegant it is to call (eval) explicitly, but in this - ;; case I couldn't avoid it - I needed a mix between a macro and a function + ;; XXX It's not elegant to call (eval) explicitly, but in this case I can't + ;; find a way to avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -70,11 +69,11 @@ ;; Same comment applies as above (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) - ;; FIXME This is going to give problems with multiple values - ;; (but will that scenario ever take place?) + ;; FIXME This gives problems with multiple values (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) + ;; TODO set numbers to 0, strings to "" (setf (,command ,game-object) NIL))))) ;; XXX This function is probably superfluous, as all place objects are stored @@ -83,3 +82,8 @@ (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." (funcall list-function object-type place))) + +(defun monster-strikes-player (monster player) + "The monster attacks a player" + ;;TODO + ) diff --git a/lisp/player.lisp b/lisp/player.lisp index b96302c..0cbe51b 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -21,12 +21,17 @@ (money 0) (item NIL) (weapon "") + (armour-class 0) (place "") (experience 0) + (level 0) (max-health 50) (health 50) (game-admin NIL)) +;; How many XP are needed to level up? +;; XXX Make this configurable in ATL? +(defvar *level-experience* 100) (defstruct race (name "") @@ -50,3 +55,15 @@ (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." (funcall list-function object-type player))) + +(defun change-player-health (player amount) + "Change the player's health points" + (incf (player-health player) amount) + (when (> 1 (player-health player)) + (error "You died!"))) ;; TODO adjust this (especially for multiplayer) + +(defun add-player-experience (player amount) + "The player gains experience points" + (incf (player-experience player) amount) + (when (zerop (rem (player-experience player) *level-experience*)) + (incf (player-level player) player))) diff --git a/lisp/ui.lisp b/lisp/ui.lisp index e5abbbf..0fafe21 100644 --- a/lisp/ui.lisp +++ b/lisp/ui.lisp @@ -111,13 +111,12 @@ ;;; Here follow the functions that define the in-game commands. ;;; - ;; A list of all in-game commands. Each new command must be registered here. (defvar *commands* '(help place player goto pickup drop talk trade - equip fight shoot + equip attack about save clear)) ;;; The following commands don't take any arguments except for a player @@ -137,8 +136,7 @@ pickup - Pick up an item lying around drop - Drop the item equip - Equip this item as your weapon -shoot - Take a shot at a monster -fight - Fight a monster +attack - Fight a monster save - Save the game to file") (format t "~A" help-text)) @@ -168,6 +166,7 @@ (player-constitution p) tab (player-dexterity p)) (format t "~&=====") (format t "~&Weapon: ~A" (player-weapon p)) + ;; XXX This will need adjusting for large item numbers (format t "~&Items: ~A" (string-from-list (player-item p))) (format t "~&=====") (format t "~&Max health: ~A~ACurrent health: ~A" @@ -187,7 +186,8 @@ ((and last-save (not game-file)) (setf game-file last-save)) ((not (or last-save game-file)) (format t "~&Where do you want to save the game?") - (input-string game-file))) + (input-string game-file) + (setf last-save game-file))) (when (y-or-n-p "Save game to ~A?" game-file) (save-world game-file) (format t "~&Game saved.")))) @@ -338,10 +338,55 @@ (format t "~&You have equipped: ~A" new-weapon)) (format t "~&Sorry, this item is not available as a weapon!"))) -(defun fight (player &optional opponent) - "The player enters combat" +(defun attack (player &optional opponent) + "The player launches an attack at a monster" (unless opponent (format t "~&Please specify an opponent!") - (return-from fight)) - ;; TODO - ) + (return-from attack)) + (unless (member opponent + (list-place-objects 'monster (player-place player)) + :test #'equalp) + (format t "~&This monster is not here!") + (return-from attack)) + (let* ((monster (get-game-object 'monster opponent)) + (m-str (monster-strength monster)) + (m-dex (monster-dexterity monster)) + (m-ac (monster-armour-class monster)) + (m-weapon (get-game-object 'weapon (monster-weapon monster))) + (p-str (player-strength player)) + (p-dex (player-dexterity player)) + (p-ac (player-armour-class player)) + (p-weapon (if (not (equalp (player-weapon player) "")) ;lbyl + (get-game-object 'weapon (player-weapon player)) + (make-weapon :name "Fists" :damage 0))) + (damage 0)) + (if (> (+ (random 10) p-dex) (+ (random 10) m-dex)) + (setf damage (calculate-damage p-str p-weapon m-dex m-ac)) + (setf damage (- 0 (calculate-damage m-str m-weapon p-dex p-ac)))) + ;(break) + (cond ((plusp damage) + (decf (monster-health monster) damage) + (format t "~&You hit! ~A points damage." damage) + (when (> 1 (monster-health monster)) + (let ((experience (round (average m-str m-dex)))) + (break) + (remove-object-attribute (player-place player) + 'monster monster) + (break) + (add-player-experience player experience) + (format t "~&You killed the monster!") + (format t "~A points experience." experience)))) + ((minusp damage) + (change-player-health player (- 0 damage)) + (format t "~&You missed. The monster hit!") + (format t "~A points damage." damage)) + (t (format t "~&You missed. The monster missed."))))) + +(defun calculate-damage (att-str att-weapon def-dex def-ac) + "A private function to calculate the damage caused by an attack" + (let ((damage 0)) + (incf damage (random att-str)) + (incf damage (weapon-damage att-weapon)) + (decf damage (random def-dex)) + (decf damage def-ac) + (if (minusp damage) 0 damage))) diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 5e29634..84553f3 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -5,6 +5,8 @@ define-monster "Fury" description "Hades' messengers, torturers, assassins. Beware!" + health 10 + dexterity 10 strength 10 aggression 30 armour-class 3 @@ -12,6 +14,7 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" + type "whip" damage 2 define-item "Anaklusmos" @@ -20,6 +23,7 @@ define-weapon "Anaklusmos" description "Riptide, a sword for heroes!" + type "sword" damage 4 define-npc "Hades" diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index 6fbcee5..080bc7e 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -31,6 +31,7 @@ (defstruct monster (name "") (description "") + (health 0) (strength 0) (dexterity 0) (aggression 0) @@ -47,18 +48,16 @@ (defstruct weapon (name "") (description "") - (ranged NIL) + (type "generic") (damage 0)) (defun set-object-attribute (game-object property value) "Set the attribute 'property' of 'game-object' to 'value'" ;; Here follows Lisp magic :D (that took ages to get right...) - ;; [And half the magic is gone after being outsourced to build-symbol :-(] - ;; I'm not sure how elegant it is to call (eval) explicitly, but in this - ;; case I couldn't avoid it - I needed a mix between a macro and a function + ;; XXX It's not elegant to call (eval) explicitly, but in this case I can't + ;; find a way to avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -70,11 +69,11 @@ ;; Same comment applies as above (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) - ;; FIXME This is going to give problems with multiple values - ;; (but will that scenario ever take place?) + ;; FIXME This gives problems with multiple values (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) + ;; TODO set numbers to 0, strings to "" (setf (,command ,game-object) NIL))))) ;; XXX This function is probably superfluous, as all place objects are stored @@ -83,3 +82,8 @@ (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." (funcall list-function object-type place))) + +(defun monster-strikes-player (monster player) + "The monster attacks a player" + ;;TODO + ) diff --git a/lisp/player.lisp b/lisp/player.lisp index b96302c..0cbe51b 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -21,12 +21,17 @@ (money 0) (item NIL) (weapon "") + (armour-class 0) (place "") (experience 0) + (level 0) (max-health 50) (health 50) (game-admin NIL)) +;; How many XP are needed to level up? +;; XXX Make this configurable in ATL? +(defvar *level-experience* 100) (defstruct race (name "") @@ -50,3 +55,15 @@ (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." (funcall list-function object-type player))) + +(defun change-player-health (player amount) + "Change the player's health points" + (incf (player-health player) amount) + (when (> 1 (player-health player)) + (error "You died!"))) ;; TODO adjust this (especially for multiplayer) + +(defun add-player-experience (player amount) + "The player gains experience points" + (incf (player-experience player) amount) + (when (zerop (rem (player-experience player) *level-experience*)) + (incf (player-level player) player))) diff --git a/lisp/ui.lisp b/lisp/ui.lisp index e5abbbf..0fafe21 100644 --- a/lisp/ui.lisp +++ b/lisp/ui.lisp @@ -111,13 +111,12 @@ ;;; Here follow the functions that define the in-game commands. ;;; - ;; A list of all in-game commands. Each new command must be registered here. (defvar *commands* '(help place player goto pickup drop talk trade - equip fight shoot + equip attack about save clear)) ;;; The following commands don't take any arguments except for a player @@ -137,8 +136,7 @@ pickup - Pick up an item lying around drop - Drop the item equip - Equip this item as your weapon -shoot - Take a shot at a monster -fight - Fight a monster +attack - Fight a monster save - Save the game to file") (format t "~A" help-text)) @@ -168,6 +166,7 @@ (player-constitution p) tab (player-dexterity p)) (format t "~&=====") (format t "~&Weapon: ~A" (player-weapon p)) + ;; XXX This will need adjusting for large item numbers (format t "~&Items: ~A" (string-from-list (player-item p))) (format t "~&=====") (format t "~&Max health: ~A~ACurrent health: ~A" @@ -187,7 +186,8 @@ ((and last-save (not game-file)) (setf game-file last-save)) ((not (or last-save game-file)) (format t "~&Where do you want to save the game?") - (input-string game-file))) + (input-string game-file) + (setf last-save game-file))) (when (y-or-n-p "Save game to ~A?" game-file) (save-world game-file) (format t "~&Game saved.")))) @@ -338,10 +338,55 @@ (format t "~&You have equipped: ~A" new-weapon)) (format t "~&Sorry, this item is not available as a weapon!"))) -(defun fight (player &optional opponent) - "The player enters combat" +(defun attack (player &optional opponent) + "The player launches an attack at a monster" (unless opponent (format t "~&Please specify an opponent!") - (return-from fight)) - ;; TODO - ) + (return-from attack)) + (unless (member opponent + (list-place-objects 'monster (player-place player)) + :test #'equalp) + (format t "~&This monster is not here!") + (return-from attack)) + (let* ((monster (get-game-object 'monster opponent)) + (m-str (monster-strength monster)) + (m-dex (monster-dexterity monster)) + (m-ac (monster-armour-class monster)) + (m-weapon (get-game-object 'weapon (monster-weapon monster))) + (p-str (player-strength player)) + (p-dex (player-dexterity player)) + (p-ac (player-armour-class player)) + (p-weapon (if (not (equalp (player-weapon player) "")) ;lbyl + (get-game-object 'weapon (player-weapon player)) + (make-weapon :name "Fists" :damage 0))) + (damage 0)) + (if (> (+ (random 10) p-dex) (+ (random 10) m-dex)) + (setf damage (calculate-damage p-str p-weapon m-dex m-ac)) + (setf damage (- 0 (calculate-damage m-str m-weapon p-dex p-ac)))) + ;(break) + (cond ((plusp damage) + (decf (monster-health monster) damage) + (format t "~&You hit! ~A points damage." damage) + (when (> 1 (monster-health monster)) + (let ((experience (round (average m-str m-dex)))) + (break) + (remove-object-attribute (player-place player) + 'monster monster) + (break) + (add-player-experience player experience) + (format t "~&You killed the monster!") + (format t "~A points experience." experience)))) + ((minusp damage) + (change-player-health player (- 0 damage)) + (format t "~&You missed. The monster hit!") + (format t "~A points damage." damage)) + (t (format t "~&You missed. The monster missed."))))) + +(defun calculate-damage (att-str att-weapon def-dex def-ac) + "A private function to calculate the damage caused by an attack" + (let ((damage 0)) + (incf damage (random att-str)) + (incf damage (weapon-damage att-weapon)) + (decf damage (random def-dex)) + (decf damage def-ac) + (if (minusp damage) 0 damage))) diff --git a/lisp/util.lisp b/lisp/util.lisp index 4437124..3ad8306 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -106,6 +106,10 @@ ;; Perhaps not very clean, but it works (eval `(,function-name ,@args))) +(defun average (&rest numbers) + "Compute the average of the given numbers" + (/ (reduce #'+ numbers) (length numbers))) + (defun keys (assoc-list) "Return a list of the keys in an association list" (if (null assoc-list) NIL @@ -145,6 +149,10 @@ (dotimes (i (length char-list) s) (setf (aref s i) (nth i char-list))))) +(defun trim-whitespace (s) + "Trim off spaces and tabs before and after string s" + (string-trim '(#\space #\tab) s)) + (defun to-string (x) "Whatever x is, convert it into a string" (cond ((stringp x) x) @@ -182,6 +190,16 @@ (dolist (line (load-text-file file-name)) (unless (null line) (format t "~%~A" line)))) +(defun write-to-file (text filename &optional (append NIL)) + "Write text (a string or list of strings) to the specified file" + (let ((text-list (if (listp text) text (list text))) + (f (if append + (open filename :direction :output :if-exists :append) + (open filename :direction :output)))) + (dolist (line text-list) + (format f "~&~A~&" line)) + (close f))) + (defun build-symbol (&rest components) "Concatenate the passed components into a single symbol" (read-from-string (string-from-list components "")))