diff --git a/dialogue.lisp b/dialogue.lisp index 8d75e27..85aa7ca 100644 --- a/dialogue.lisp +++ b/dialogue.lisp @@ -18,10 +18,11 @@ ;;;; the primary gameplay, the RPG-ish-ish bits). (defpackage :flora-search-aurora.dialogue - (:nicknames :fsa.d :dialogue) + (:nicknames :fsa.dia :dialogue :πŸ’¬) (:use :cl :flora-search-aurora.overworld :flora-search-aurora.ui :flora-search-aurora.input) - (:export #:dialogue-state #:say)) + (:export #:dialogue-state + #:start-dialogue #:face #:say #:mumble)) (in-package :flora-search-aurora.dialogue) @@ -30,38 +31,46 @@ ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” ;;; Dialogue-generation DSL (sorta) ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” -(defun dialogue (&rest dialogue-tree) +(defun start-dialogue (&rest dialogue-tree) (reduce (lambda (a b) (append a b)) dialogue-tree)) -(defun say (speaker text) - (list - (list :speaker speaker :text text :face 'talking-face) - (car (face speaker 'normal-face)))) - - -(defun mumble (speaker text) - (list - (list :speaker speaker :text text))) - - (defun face (speaker face) (list (list :speaker speaker :face face))) +(defun say (speaker text) + (list + (list :speaker speaker :text text :face 'talking-face :progress 0) + (car (face speaker 'normal-face)))) + + +(defun mumble (speaker text) + (list + (list :speaker speaker :text text :progress 0))) + + + + ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” ;;; Dialogue logic ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” (defun pressed-enter-p () + "Whether or not the enter/return key has been pressed recently." (and (listen) (eq (getf (normalize-char-plist (read-char-plist)) :char) #\return))) (defun appropriate-face (map speaker face) + "Return the face appropriate for the speaker. +If FACE is a string, used that. +If FACE is 'TALKING-FACE, then use their talking-face (if they have one). +If FACE is 'NORMAL-FACE, then use their normal-face (if they’ve got one). +If FACE is NIL… guess what that does. :^)" (let ((talking-face (getf-entity-data map speaker :talking-face)) (normal-face (getf-entity-data map speaker :normal-face))) (cond ((and (eq face 'talking-face) @@ -74,27 +83,43 @@ face)))) -(defun dialogue-state-update (dialogue-list map) - "The logic/input-processing helper function for DIALOGUE-STATE." - (let* ((speaker (intern (string-upcase (getf (car dialogue-list) :speaker)))) - (new-face (appropriate-face map speaker - (getf (car dialogue-list) :face)))) +(defun update-speaking-face (map dialogue) + "Given a line (plist) of dialogue, change speaker’s face to either their +talking-face or the face given by the dialogue." + (let* ((speaker (intern (string-upcase (getf dialogue :speaker)))) + (new-face (appropriate-face map speaker (getf dialogue :face)))) ;; Replace the face, when appropriate. (when new-face - (setf (getf-entity-data map speaker :face) new-face))) - ;; Progress the dialogue as appropriate. + (setf (getf-entity-data map speaker :face) new-face)))) + + +(defun progress-line-delivery (dialogue) + "Progress the delivery of a line (plist) of dialogue. That is, increment the +β€œsaid character-count” :PROGRESS, which dictates the portion of the message that +should be printed on the screen at any given moment." + (let ((progress (getf dialogue :progress)) + (text (getf dialogue :text))) + (when (and text + (< progress (length text))) + (incf (getf dialogue :progress))))) + + +(defun dialogue-state-update (map dialogue-list) + "The logic/input-processing helper function for DIALOGUE-STATE." + (update-speaking-face map (car dialogue-list)) + (progress-line-delivery (car dialogue-list)) + ;; Progress to the next line of dialogue as appropriate. (let ((text (getf (car dialogue-list) :text))) (cond ((or (pressed-enter-p) (not text)) (if (cdr dialogue-list) - (list :dialogue (cdr dialogue-list) :map map) - (values nil - (list :map map)))) + (list :dialogue (cdr dialogue-list) :map map) + (progn + (✎:hide-cursor) + (values nil + (list :map map))))) ((cdr dialogue-list) - (list :dialogue dialogue-list :map map)) - ('t - (values nil - (list :map map)))))) + (list :dialogue dialogue-list :map map))))) @@ -104,9 +129,11 @@ (defun dialogue-state-draw (matrix dialogue-list) "Draw the dialogue where appropriate. Helper function for DIALOGUE-STATE." - (let ((text (getf (car dialogue-list) :text))) + (let ((text (getf (car dialogue-list) :text)) + (progress (getf (car dialogue-list) :progress))) (when text - (render-line matrix text 0 0)))) + (✎:show-cursor) + (render-string-partially matrix text 0 0 :char-count progress)))) @@ -126,4 +153,4 @@ entities as the speakers. Dialogue should be in the format: A state-function for use with STATE-LOOP." (sleep .02) (dialogue-state-draw matrix dialogue) - (dialogue-state-update dialogue map)) + (dialogue-state-update map dialogue)) diff --git a/display.lisp b/display.lisp index 1dc2736..7e18433 100644 --- a/display.lisp +++ b/display.lisp @@ -17,9 +17,10 @@ ;;;; All display-related curses go here. (defpackage :flora-search-aurora.display + (:nicknames :fsa.d :display :✎) (:use :cl) (:export #:make-screen-matrix #:print-screen-matrix #:matrix-delta - #:clear-screen)) + #:hide-cursor #:show-cursor #:clear-screen)) (in-package :flora-search-aurora.display) @@ -51,7 +52,7 @@ The body has access to 4 variables: (defun matrix-delta (a b) "Given two 2D matrices, return a matrix containing only the cells -that change between aβ†’b (favouring those in b) β€” all others are nil." +that change between Aβ†’B (favouring those in B) β€” all others are nil." (let ((delta (make-array (array-dimensions a)))) (do-for-cell a (when (not (eq cell @@ -66,9 +67,7 @@ that change between aβ†’b (favouring those in b) β€” all others are nil." (do-for-cell matrix (when (characterp cell) (move-cursor (+ i 1) (+ j 1)) - (write-char cell))) - (destructuring-bind (i j) (array-dimensions matrix) - (move-cursor i j))) + (write-char cell)))) (defun make-screen-matrix () @@ -81,6 +80,14 @@ that change between aβ†’b (favouring those in b) β€” all others are nil." ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” ;;; Misc. utils ;;; β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” +(defun hide-cursor () + (cl-charms/low-level:curs-set 0)) + + +(defun show-cursor () + (cl-charms/low-level:curs-set 1)) + + (defun move-cursor (row column &key (stream *standard-output*)) "Moves cursor to desired position. Borrowed from https://github.com/gorozhin/chlorophyll/ diff --git a/flora-search-aurora.lisp b/flora-search-aurora.lisp index 8cf410c..3166ce5 100644 --- a/flora-search-aurora.lisp +++ b/flora-search-aurora.lisp @@ -28,6 +28,7 @@ (load "dialogue.lisp") (defpackage :flora-search-aurora + (:nicknames :fsa :✿) (:export #:main) (:use :cl :flora-search-aurora.input :flora-search-aurora.display @@ -41,9 +42,9 @@ (defun literary-girl-dialogue (map) (lambda (matrix &key (map map) - (dialogue (dialogue::dialogue - (dialogue::say "literary-girl" "Oh, hello.") - (dialogue::say "player" "What, no quip?")))) + (dialogue (πŸ’¬:start-dialogue + (πŸ’¬:say "literary-girl" "Blah blah, testing. A multi-lined one. For real! jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj akls djlaks jdlaksj dlakjsd") + (πŸ’¬:say "player" "ktp ktp jes jes?")))) (overworld-state-draw matrix map) (dialogue-state matrix :map map :dialogue dialogue))) @@ -56,7 +57,7 @@ Given a list of state-functions, STATES, it will execute the first function. Each state-function must take at least a single parameter, a matrix of characters. A state-function should edit this matrix in-place, replacing its elements with characters that will later be printed to the terminal. -What the state-function returns is pretty important: +What the state-function returns is pretty important, having different repercussions: * NIL β€” The function is removed from STATES, and so the next function in STATES will start getting executed instead. * NIL; List β€” The function is popped off STATES and the list is used as the new parameters for @@ -124,10 +125,10 @@ with STATE-LOOP." (defun main () "A pathetic fascimile of a main loop. What does it do? WHAST DOES TI DODOO?" (cl-charms:with-curses () - (cl-charms/low-level:curs-set 0) ;; Hide the terminal cursor - (cl-charms:enable-raw-input :interpret-control-characters 't) - (clear-screen) - (state-loop (list (make-main-menu-state))))) + (cl-charms:enable-raw-input :interpret-control-characters 't) + (hide-cursor) + (clear-screen) + (state-loop (list (make-main-menu-state))))) (main) ;; β€” Knock-knock diff --git a/res/map.tmx b/res/map.tmx index 5044a82..335cfac 100644 --- a/res/map.tmx +++ b/res/map.tmx @@ -98,18 +98,20 @@ - + + - + + diff --git a/ui.lisp b/ui.lisp index 9c9dcc3..4d712c5 100644 --- a/ui.lisp +++ b/ui.lisp @@ -18,9 +18,10 @@ ;;;; Let's get to it, we're on a deadline! (defpackage :flora-search-aurora.ui + (:nicknames :fsa.u :ui) (:use :cl :flora-search-aurora.display :flora-search-aurora.input :assoc-utils) (:export #:menu-state - #:render-line + #:render-line #:render-string #:render-string-partially :label :selection :selected)) (in-package :flora-search-aurora.ui) @@ -127,16 +128,31 @@ The item list should be an alist of the following format: "Render the given string to the matrix of characters, character-by-character. Will line-break or truncate as appropriate and necessary to not exceed the positional arguments nor the dimensions of the matrix." + (render-string-partially matrix text x y :max-column max-column :max-row max-row + :char-count (length text))) + + +(defun render-string-partially (matrix text x y &key (char-count 0) (max-column 72) (max-row 20)) + "Partially render the given string to a matrix of characters. Will render only +a portion of the string, dictated by the CHAR-COUNT. +See the similar RENDER-STRING function." (let* ((dimensions (array-dimensions matrix)) (max-column (at-most (cadr dimensions) max-column)) - (max-row (at-most (car dimensions) max-row)) + (max-write-row (at-most (at-most (car dimensions) max-row) + (floor (/ char-count max-column)))) + (max-column-at-max-write-row (- char-count (* max-write-row max-column))) (substrings (split-string-by-length text (- max-column x))) (row 0)) (loop while (and (<= (+ y row) max-row) substrings) - do - (render-line matrix (pop substrings) - x (+ y row)) + do (cond ((< row max-write-row) + (render-line matrix (pop substrings) + x (+ y row))) + ((eq row max-write-row) + (render-line matrix (subseq (pop substrings) 0 max-column-at-max-write-row) + x (+ y row))) + ('t + (pop substrings))) (incf row))) matrix)