diff --git a/blast/blast.lisp b/blast/blast.lisp index 26c771d..78b39af 100644 --- a/blast/blast.lisp +++ b/blast/blast.lisp @@ -28,18 +28,18 @@ (defpackage :blast (:documentation "Blast Tactics: A sci-fi roguelike for Common Lisp.") - (:use :rlx :common-lisp) + (:use :xe2 :common-lisp) (:export blast)) (in-package :blast) ;;; Custom bordered viewport -(define-prototype view (:parent rlx:=viewport=)) +(define-prototype view (:parent xe2:=viewport=)) (define-method render view () [parent>>render self] - (rlx:draw-rectangle 0 0 + (xe2:draw-rectangle 0 0 :color ".blue" :destination )) @@ -48,7 +48,7 @@ ;;; Controlling the game. -(define-prototype blast-prompt (:parent rlx:=prompt=)) +(define-prototype blast-prompt (:parent xe2:=prompt=)) (defparameter *basic-keybindings* '(("KP7" nil "move :northwest .") @@ -167,7 +167,8 @@ (defparameter *alternate-qwerty-keybindings* (append *basic-keybindings* - '(("Q" nil "move :northwest .") + '( + ("Q" nil "move :northwest .") ("W" nil "move :north .") ("E" nil "move :northeast .") ("A" nil "move :west .") @@ -270,7 +271,7 @@ ("Q" (:control) "quit .")))) (define-method install-keybindings blast-prompt () - (let ((keys (ecase rlx:*user-keyboard-layout* + (let ((keys (ecase xe2:*user-keyboard-layout* (:qwerty *qwerty-keybindings*) (:alternate-qwerty *alternate-qwerty-keybindings*) (:dvorak *dvorak-keybindings*)))) @@ -285,7 +286,7 @@ (defvar *ship-status* nil) (defvar *dude-status* nil) -(define-prototype status (:parent rlx:=formatter=) +(define-prototype status (:parent xe2:=formatter=) (character :documentation "The character cell.")) (define-method set-character status (character) @@ -441,7 +442,7 @@ (define-prototype splash (:parent =widget=)) (define-method render splash () - (rlx:draw-resource-image "splash" 0 0 + (xe2:draw-resource-image "splash" 0 0 :destination )) (defvar *space-bar-function*) @@ -452,7 +453,7 @@ (when (functionp *space-bar-function*) (funcall *space-bar-function*)) ;; TODO ugh this is a hack! - (rlx:show-widgets)) + (xe2:show-widgets)) (define-prototype splash-prompt (:parent =prompt=) (default-keybindings :initform '(("SPACE" nil "dismiss .")))) @@ -469,14 +470,14 @@ (defparameter *tile-size* 16) (defun blast () - (rlx:message "Initializing Blast Tactics...") + (xe2:message "Initializing Blast Tactics...") (setf clon:*send-parent-depth* 2) - (rlx:set-screen-height *blast-window-height*) - (rlx:set-screen-width *blast-window-width*) - ;; (rlx:set-frame-rate 30) - ;; (rlx:disable-timer) - ;; (rlx:enable-held-keys 1 15) - (setf rlx:*zoom-factor* 1) + (xe2:set-screen-height *blast-window-height*) + (xe2:set-screen-width *blast-window-width*) + ;; (xe2:set-frame-rate 30) + ;; (xe2:disable-timer) + ;; (xe2:enable-held-keys 1 15) + (setf xe2:*zoom-factor* 1) (let* ((prompt (clone =blast-prompt=)) (universe (clone =universe=)) (narrator (clone =narrator=)) @@ -492,10 +493,10 @@ (stack (clone =stack=)) (stack2 (clone =stack=))) ;; hehe, turn this on for realtime - ;; (rlx:enable-timer) - ;; (rlx:set-frame-rate 30) - ;; (rlx:set-timer-interval 1) - ;; (rlx:enable-held-keys 1 3) + ;; (xe2:enable-timer) + ;; (xe2:set-frame-rate 30) + ;; (xe2:set-timer-interval 1) + ;; (xe2:enable-held-keys 1 3) ;; (setf *view* (clone =view=)) ;; @@ -586,11 +587,11 @@ ;; (draw-line x y sx sy :destination image ;; :color color))))) ;; [add-overlay *view* #'hack-overlay]))) - ;; (setf rlx::*lighting-hack-function* #'light-hack)) + ;; (setf xe2::*lighting-hack-function* #'light-hack)) ;; (setf *pager* (clone =pager=)) [auto-position *pager*]; :width *left-column-width*] - (rlx:install-widgets splash-prompt splash) + (xe2:install-widgets splash-prompt splash) [add-page *pager* :play stack prompt dude-status ship-status *view* minimap terminal] [add-page *pager* :help textbox])) diff --git a/blast/enemy.lisp b/blast/enemy.lisp index 668f505..eb13de6 100644 --- a/blast/enemy.lisp +++ b/blast/enemy.lisp @@ -55,7 +55,7 @@ of poisonous radioactive gas.")) (when (and (< (random 100) probability) [in-bounds-p *active-world* r c]) [drop-cell *active-world* (clone =explosion=) r c :no-collisions nil])))) - (dolist (dir rlx:*compass-directions*) + (dolist (dir xe2:*compass-directions*) (multiple-value-bind (r c) (step-in-direction dir) (boom r c 100))) @@ -228,7 +228,7 @@ Not the typical choice of the best pilots.")) (stepping :initform t) (attacking-with :initform :robotic-arm) (max-weight :initform (make-stat :base 25)) - (direction :initform (rlx:random-direction)) + (direction :initform (xe2:random-direction)) (strength :initform (make-stat :base 4 :min 0 :max 30)) (dexterity :initform (make-stat :base 5 :min 0 :max 30)) (intelligence :initform (make-stat :base 11 :min 0 :max 30)) @@ -250,7 +250,7 @@ Berserkers attack with a shock probe.")) [>>attack self player-dir] [>>move self player-dir])) (progn (when [obstacle-in-direction-p world row column ] - (setf (rlx:random-direction))) + (setf (xe2:random-direction))) [>>move self ]))))) (define-method die berserker () @@ -269,7 +269,7 @@ Berserkers attack with a shock probe.")) ;;; The radar-equipped Biclops is more dangerous. -(define-prototype biclops (:parent rlx:=cell=) +(define-prototype biclops (:parent xe2:=cell=) (name :initform "Biclops") (strength :initform (make-stat :base 15 :min 0 :max 50)) (dexterity :initform (make-stat :base 15 :min 0 :max 30)) @@ -464,7 +464,7 @@ Hard to kill because of their evasive manuevers.")) (square (nth (position farthest scores) neighbors))) (destructuring-bind (r c) square - [move self (rlx:direction-to row column r c)]))))))) + [move self (xe2:direction-to row column r c)]))))))) (define-method move rook (direction) (setf direction) @@ -627,5 +627,5 @@ and attacks anyone who comes near.")) [>>speedsuck self [resolve self player-dir]] [>>move self player-dir])) (progn (when [obstacle-in-direction-p world row column ] - (setf (rlx:random-direction))) + (setf (xe2:random-direction))) [>>move self ]))))) diff --git a/forest/base.lisp b/forest/base.lisp index 21d32e8..ebc86a5 100644 --- a/forest/base.lisp +++ b/forest/base.lisp @@ -28,6 +28,7 @@ Monastery. It is cold and rainy." :fireflies 200 :graveyards 4 :ruins 3 + :firewood 10 :raining t :tree-grain 0.5 :tree-density 30 @@ -47,6 +48,8 @@ It has begun to snow." :graveyards 6 :ruins 10 :snowing t + :firewood 25 + :archer-skeletons 6 :tree-grain 0.2 :tree-density 30 :water-grain 0.5 diff --git a/forest/death-alien.wav b/forest/death-alien.wav new file mode 100644 index 0000000..fdacac3 Binary files /dev/null and b/forest/death-alien.wav differ diff --git a/forest/enemy.lisp b/forest/enemy.lisp index 43e4794..df3d462 100644 --- a/forest/enemy.lisp +++ b/forest/enemy.lisp @@ -41,6 +41,7 @@ (intelligence :initform (make-stat :base 13 :min 0 :max 30)) (categories :initform '(:actor :target :obstacle :opaque :enemy :equipper)) (equipment-slots :initform '(:left-hand)) + (bow-reload-clock :initform 0) (max-items :initform (make-stat :base 3)) (stepping :initform t) (speed :initform (make-stat :base 1)) @@ -87,6 +88,81 @@ [play-sample self "dead"] [parent>>die self]) +;;; The deadly archer skeleton + +(defcell archer-skeleton + (tile :initform "archer-skeleton") + (name :initform "Archer-Skeleton") + (categories :initform '(:obstacle :actor :equipper :opaque + :exclusive :enemy :target :archer-skeleton)) + (direction :initform nil) + (speed :initform (make-stat :base 3)) + (movement-cost :initform (make-stat :base 6)) + (hit-points :initform (make-stat :base 16 :min 0)) + (equipment-slots :initform '(:left-hand :right-hand)) + (arrows :initform (make-stat :base 15 :min 0)) + (max-items :initform (make-stat :base 3)) + (stepping :initform t) + (dead :initform nil) + (firing-with :initform :left-hand) + (energy :initform (make-stat :base 800 :min 0 :max 1000)) + (strength :initform (make-stat :base 24)) + (dexterity :initform (make-stat :base 12)) + (description :initform +"An undead soul inhabits these blackened bones.")) + +(define-method choose-new-direction archer-skeleton () + [expend-action-points self 2] + (setf + (if (= 0 (random 20)) + ;; occasionally choose a random dir + (nth (random 3) + '(:north :south :east :west)) + ;; otherwise turn left + (getf '(:north :west :west :south :south :east :east :north) + (or :north))))) + +(define-method loadout archer-skeleton () + [choose-new-direction self] + [make-inventory self] + [make-equipment self] + [equip self [add-item self (clone =wooden-bow=)]]) + +(define-method cancel archer-skeleton () + (decf *enemies*)) + +;; (define-method initialize archer-skeleton () +;; [make-inventory self] +;; [make-equipment self]) + +(define-method kick archer-skeleton (direction) + (setf direction)) + +(define-method run archer-skeleton () + (clon:with-field-values (row column) self + (let ((world *world*)) + (if (and (< [distance-to-player world row column] 10) + [line-of-sight world row column + [player-row world] + [player-column world]]) + (let ((player-dir [direction-to-player world row column])) + [move self player-dir] + [fire self player-dir] + [expend-action-points self 10]) + (multiple-value-bind (r c) + (step-in-direction ) + [expend-action-points self 8] + (when [obstacle-at-p world r c] + [choose-new-direction self]) + [move self ]))))) + +(define-method die archer-skeleton () + (unless + (setf t) + [drop self (clone =arrows= :count [stat-value self :arrows])] + [play-sample self "death-alien"] + [delete-from-world self])) + ;;; Wolves are the most difficult enemies. (defcell wolf diff --git a/forest/forest.fasl b/forest/forest.fasl index 4888ba8..7130083 100644 Binary files a/forest/forest.fasl and b/forest/forest.fasl differ diff --git a/forest/forest.lisp b/forest/forest.lisp index faefc6e..5d13a3d 100644 --- a/forest/forest.lisp +++ b/forest/forest.lisp @@ -394,10 +394,11 @@ (graveyards 15) (ruins 15) (herbs 2) - (firewood 8) + (firewood 14) level snowing raining (tree-grain 0.3) (tree-density 30) + (archer-skeletons 0) (water-grain 0.9) (water-density 90) (water-cutoff 0.2)) @@ -433,6 +434,9 @@ (dotimes (n firewood) (multiple-value-bind (r c) [random-place self] [drop-cell self (clone =firewood=) r c :exclusive t :probe t])) + (dotimes (n archer-skeletons) + (multiple-value-bind (r c) [random-place self] + [drop-cell self (clone =archer-skeleton=) r c :exclusive t :probe t :loadout t])) (let* ((gateway (clone (ecase level (1 =river-gateway=) (2 =passage-gateway=)))) diff --git a/forest/forest.org b/forest/forest.org index 845d884..f0626b5 100644 --- a/forest/forest.org +++ b/forest/forest.org @@ -1,9 +1,6 @@ -** TODO [#A] fix being able to travel easily thru water -*** TODO smother fire with dirt -*** TODO freezing death -*** TODO hungry/soaked/freezing display in status -** TODO making fire to dry yourself -** TODO [#A] fix too much water +** TODO faster skel archers with better vision +** TODO hungry/soaked/freezing display in status +** TODO rose found in the forest ** TODO [#A] make wolves less annoying ** TODO more herbs on dead bodies' ** TODO more skeletons @@ -15,21 +12,11 @@ ** TODO magic barrier shield (flickering sprites) ** TODO magic missile (sparkly trails.) ** TODO enemies search for dark spots -** TODO [#A] fix moving when dead ** TODO [#B] fix scrolling -** TODO [#A] fix help screen ** TODO [#C] Command-Q should quit on mac ? ** TODO [#A] don't place bodies in walls OR ITEMS -** TODO [#A] fix arrows not dying ** TODO [#C] indicate edge of map with chevrons. piece together maps. ** TODO [#C] overworld map? (FUTURE) ** TODO [#B] fix in-game map scrolling off bottom of viewport ** TODO [#B] find in-game notes with same color scheme text as scroll ** TODO [#B] longer level approach to monastery, talk to a few npcs, follow a road and cross fences -cold rainy forest -brush to slow progress -bow and arrow combat , food factor, rest. travel a long road. -bird sound. short story. exploring an abandoned house. path through -woods to clearing. follow the bird. watercolor look. -lighting effects. game occurs, you explore ruins at (night , re-use vm0 art -lightning bugs diff --git a/forest/forest.pak b/forest/forest.pak index ed10b53..9654b9b 100644 --- a/forest/forest.pak +++ b/forest/forest.pak @@ -81,6 +81,7 @@ (:name "wolf" :type :image :file "wolf.png") (:name "raindrop" :type :image :file "raindrop.png") (:name "skeleton" :type :image :file "skel.png") + (:name "archer-skeleton" :type :image :file "skel-archer.png") (:name "gravestone" :type :image :file "gravestone.png") (:name "wooden-bow" :type :image :file "wooden-bow.png") @@ -97,6 +98,8 @@ (:name "wall" :type :image :file "wall.png") (:name "dandelion" :type :image :file "dandelion.png") (:name "death" :type :sample :file "death.wav" :properties (:volume 40)) + (:name "death-alien" :type :sample :file "death-alien.wav" :properties (:volume 30)) + (:name "knock" :type :sample :file "knock.wav" :properties (:volume 50)) (:name "monks" :type :sample :file "monks.wav" :properties (:volume 25)) (:name "talk" :type :sample :file "talk.wav" :properties (:volume 10)) (:name "lutey" :type :sample :file "lutey.wav" :properties (:volume 31)) diff --git a/forest/knock.wav b/forest/knock.wav new file mode 100644 index 0000000..0900dda Binary files /dev/null and b/forest/knock.wav differ diff --git a/forest/player.lisp b/forest/player.lisp index c5d2a94..8b4f208 100644 --- a/forest/player.lisp +++ b/forest/player.lisp @@ -27,8 +27,11 @@ (defcell arrows (tile :initform "arrows") - (count :initform (+ 6 (random 18)))) + (count :initform nil)) +(define-method initialize arrows (&key (count (+ 5 (random 12)))) + (setf count)) + (define-method step arrows (stepper) (when [is-player stepper] [say self "You found ~S arrows." ] @@ -108,6 +111,7 @@ (let ((target [category-in-direction-p *world* :target])) (when target [damage target 3] + [play-sample self "knock"] [die self]) (if [obstacle-in-direction-p *world* ] [die self] @@ -121,7 +125,7 @@ (define-method step arrow (stepper) (when [is-player stepper] - [say self "This arrow is still good. You add it to your quiver."] + [say stepper "This arrow is still good. You add it to your quiver."] [stat-effect stepper :arrows 1] [delete-from-world self])) @@ -160,6 +164,8 @@ (defparameter *freezing-damage-clock* 12) +(defparameter *bow-reload-clock* 10) + (defcell player (tile :initform "player") (description :initform "You are an archer and initiate monk of the Sanctuary Order.") @@ -170,6 +176,7 @@ (hunger-damage-clock :initform 0) (freezing :initform (make-stat :base 0 :min 0 :max 300)) (freezing-damage-clock :initform 0) + (bow-reload-clock :initform 0) (hearing-range :initform 1000) (firing-with :initform :left-hand) (arrows :initform (make-stat :base 20 :min 0 :max 40)) @@ -186,6 +193,15 @@ (stepping :initform t) (categories :initform '(:actor :player :obstacle :target))) +(define-method fire player (direction) + (if (zerop ) + (progn + (setf *bow-reload-clock*) + [add-category self :reloading] + [parent>>fire self direction]) + (progn + [say self "Still reloading; cannot fire."]))) + (define-method eat player () (if (zerop [stat-value self :rations]) [say self "You don't have any rations to eat."] @@ -194,6 +210,10 @@ [stat-effect self :hunger -900] [stat-effect self :rations -1]))) +(define-method move player (direction) + (unless + [parent>>move self direction])) + (define-method use-item player (n) (assert (integerp n)) (let ((object [item-at self n])) @@ -223,55 +243,71 @@ (xe2:quit :shutdown)) (define-method run player () - (message "FREEZING: ~A" [stat-value self :freezing]) - [stat-effect self :hunger 1] - (let ((hunger [stat-value self :hunger]) - (freezing [stat-value self :freezing])) - (when (= *hunger-warn* hunger) - [say self "You are getting hungry. Press Control-E to eat a ration."]) - (when (= *hunger-warn-2* hunger) - [emote self '((("I'm very hungry.")))] - [say self "You are getting extremely hungry! Press Control-E to eat a ration."]) - (when (= *hunger-max* hunger) - (if (minusp ) - (progn - [say self "You are starving! You will die if you do not eat soon."] - [say self "Press Control-E to eat a ration."] - (setf *hunger-damage-clock*) - [damage self 1]) - (decf ))) - (when (= *freezing-warn* freezing) - [say self "You are beginning to get soaked."]) - (when (= *freezing-warn-2* freezing) - [say self "You are getting soaked! You will begin to freeze soon."]) - (when (= *freezing-max* freezing) - (if (minusp ) - (progn - [say self "You are freezing! You will die if you do not dry out soon."] - [say self "Press Control-C to make a campfire."] - (setf *freezing-damage-clock*) - [damage self 1]) - (decf ))) - (when (< [stat-value self :hit-points] 10) - [narrateln :narrator "LOW HEALTH WARNING! You will die soon if you do not heal." :foreground ".red"]) - (when (zerop [stat-value self :hit-points]) - [die self]) - (when (and *status* ) [update *status*]))) + (unless + (message "FREEZING: ~A" [stat-value self :freezing]) + [stat-effect self :hunger 1] + (let ((hunger [stat-value self :hunger]) + (freezing [stat-value self :freezing])) + (when (= *hunger-warn* hunger) + [say self "You are getting hungry. Press Control-E to eat a ration."]) + (when (= *hunger-warn-2* hunger) + [emote self '((("I'm very hungry.")))] + [say self "You are getting extremely hungry! Press Control-E to eat a ration."]) + (when (= *hunger-max* hunger) + (if (minusp ) + (progn + [say self "You are starving! You will die if you do not eat soon."] + [say self "Press Control-E to eat a ration."] + (setf *hunger-damage-clock*) + [damage self 1]) + (decf ))) + (if (> hunger *hunger-warn*) + [add-category self :hungry] + [delete-category self :hungry]) + (if (= *hunger-max* hunger) + [add-category self :starving] + [delete-category self :starving]) + (when (= *freezing-warn* freezing) + [say self "You are beginning to get soaked."]) + (when (= *freezing-warn-2* freezing) + [say self "You are getting soaked! You will begin to freeze soon."]) + (when (= *freezing-max* freezing) + (if (minusp ) + (progn + [say self "You are freezing! You will die if you do not dry out soon."] + [say self "Press Control-C to make a campfire."] + (setf *freezing-damage-clock*) + [damage self 1]) + (decf ))) + (if (= *freezing-max* freezing) + [add-category self :freezing] + [delete-category self :freezing]) + (if (< [stat-value self :hit-points] 10) + (progn [>>narrateln :narrator "LOW HEALTH WARNING! You will die soon if you do not heal." :foreground ".red"] + [add-category self :dying]) + [delete-category self :dying]) + (when (zerop [stat-value self :hit-points]) + [die self]) + (setf (max 0 (- 1))) + (when (zerop ) [delete-category self :reloading]) + (when (and *status* ) [update *status*])))) (define-method restart player () - (let ((player (clone =player=))) - [destroy *universe*] - [set-player *universe* player] - [set-character *status* player] - [play *universe* - :address (generate-level-address 1)] - [loadout player])) + (when + (let ((player (clone =player=))) + [destroy *universe*] + [set-player *universe* player] + [set-character *status* player] + [play *universe* + :address (generate-level-address 1)] + [loadout player]))) (define-method damage player (points) - [say self "You take ~A hit ~A of damage." - points (if (= 1 points) "point" "points")] - (percent-of-time 70 [play-sample self (car (one-of '("unh-1" "unh-2" "unh-3")))]) - [stat-effect self :hit-points (- points)]) + (unless + [say self "You take ~A hit ~A of damage." + points (if (= 1 points) "point" "points")] + [play-sample self (car (one-of '("unh-1" "unh-2" "unh-3")))] + [stat-effect self :hit-points (- points)])) (define-method die player () (unless diff --git a/forest/skel-archer.png b/forest/skel-archer.png new file mode 100644 index 0000000..31d33c2 Binary files /dev/null and b/forest/skel-archer.png differ diff --git a/forest/skel.png b/forest/skel.png index 3149ebd..9ee9ba6 100644 Binary files a/forest/skel.png and b/forest/skel.png differ diff --git a/forest/startup.lisp b/forest/startup.lisp index a2520b7..a3f94ea 100644 --- a/forest/startup.lisp +++ b/forest/startup.lisp @@ -242,7 +242,9 @@ [print-stat self :defense :warn-below 10] [print self " "] [print-stat self :speed :warn-below 2] - [println self " "] + [print self " "] + [newline self] + ;; [print self " Equipment: "] [print-equipment-slot self :right-hand] [print-equipment-slot self :left-hand] @@ -251,13 +253,27 @@ [print self (format nil " RATIONS: ~S " [stat-value char :rations])] [print self nil :image "ration"] [print self (format nil " FIREWOOD: ~S " [stat-value char :firewood])] - [println self nil :image "firewood-1"] + [print self nil :image "firewood-1"] [newline self] + ;; [print self " Inventory: "] [print-inventory-slot self 0 :show-as 1] [print-inventory-slot self 1 :show-as 2] [print-inventory-slot self 2 :show-as 3] [print-inventory-slot self 3 :show-as 4] + [newline self] + ;; status ailments + (when [in-category char :freezing] + [print self " Freezing " :foreground ".blue"]) + (when [in-category char :hungry] + [print self " Hungry " :foreground ".green"]) + (when [in-category char :starving] + [print self " Starving " :foreground ".red"]) + (when [in-category char :dying] + [print self " Dying " :foreground ".yellow" :background ".red"]) + (when [in-category char :reloading] + [print self " Reloading " :foreground ".black" :background ".yellow"]) + [print self " "] [newline self])) ;;; Pager and splash screen @@ -304,7 +320,7 @@ (defparameter *room-window-width* 800) (defparameter *room-window-height* 600) -(defparameter *start-level* 2) +(defparameter *start-level* 1) (defun init-forest () (xe2:message "Initializing Forest...") @@ -337,7 +353,7 @@ ;; (labels ((spacebar () (setf *status* status) - [resize status :height 60 :width 800] + [resize status :height 80 :width 800] [move status :x 5 :y 0] [set-character status player] ;; @@ -365,8 +381,8 @@ :prompt prompt :viewport viewport] [set-tile-size viewport 16] - [resize viewport :height 470 :width *room-window-width*] - [move viewport :x 0 :y 60] + [resize viewport :height 450 :width *room-window-width*] + [move viewport :x 0 :y 80] [set-origin viewport :x 0 :y 0 :height (truncate (/ (- *room-window-height* 200) 16)) :width (truncate (/ *room-window-width* 16))] @@ -380,7 +396,6 @@ (let ((text (find-resource-object "help-message"))) [set-buffer help text]) ;; - ;; (setf *pager* (clone =pager=)) [auto-position *pager*] (xe2:install-widgets splash-prompt splash)