Legiblify :šŸ’¬; finish dialogue-printing with ENTER

Like with RPGs!
This commit is contained in:
Jaidyn Ann 2023-06-21 20:31:29 -05:00
parent fe9b74a600
commit 666c155f95
2 changed files with 69 additions and 29 deletions

View File

@ -28,7 +28,23 @@
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
;;; Dialogue-generation DSL (sorta) ;;; Misc. utilities
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
(defun pressed-enter-p ()
"Whether or not the enter/return key has been pressed recently.
Man, todayā€™s a good day. Well, it wasnā€™t great, too be honest. Kind of bad,
I slightly humiliated myself a tiny bit. But wow, Iā€™m having such nice tea!
Programming with nice tea! What a nice day this is. If you happen to be
reading this, I hope your day is going well too!
If not, have some tea on me: Iā€™m paying. =w="
(and (listen)
(eq (getf (āŒØ:normalize-char-plist (āŒØ:read-char-plist)) :char)
#\return)))
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
;;; Dialogue-generation helpers
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
(defun start-dialogue (&rest dialogue-tree) (defun start-dialogue (&rest dialogue-tree)
(reduce (lambda (a b) (append a b)) (reduce (lambda (a b) (append a b))
@ -56,6 +72,8 @@
;;; Accessors ;;; Accessors
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
(defun dialogue-speaker (dialogue) (defun dialogue-speaker (dialogue)
"Get the DIALOGUE-speakerā€™s corresponding identifying symbol.
Because theyā€™re stored in strings. So we gotta, like, unstringify. Ya dig?"
(intern (string-upcase (getf dialogue :speaker)))) (intern (string-upcase (getf dialogue :speaker))))
@ -63,13 +81,6 @@
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
;;; Dialogue logic ;;; 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) (defun appropriate-face (map speaker face)
"Return the face appropriate for the speaker. "Return the face appropriate for the speaker.
If FACE is a string, used that. If FACE is a string, used that.
@ -110,19 +121,27 @@ should be printed on the screen at any given moment."
(defun dialogue-state-update (map dialogue-list) (defun dialogue-state-update (map dialogue-list)
"The logic/input-processing helper function for DIALOGUE-STATE." "The logic/input-processing helper function for DIALOGUE-STATE.
Progress through the lines of dialogue when the user hits ENTER, etc.
Returns the state for use with STATE-LOOP, pay attention!"
(update-speaking-face map (car dialogue-list)) (update-speaking-face map (car dialogue-list))
(progress-line-delivery (car dialogue-list)) (progress-line-delivery (car dialogue-list))
;; Progress to the next line of dialogue as appropriate. ;; Progress to the next line of dialogue as appropriate.
(let ((text (getf (car dialogue-list) :text))) (let* ((text (getf (car dialogue-list) :text))
(cond ((or (pressed-enter-p) (finished-printing-p (eq (length text)
(not text)) (getf (car dialogue-list) :progress)))
(did-press-enter-p (pressed-enter-p)))
(cond ((or (not text)
(and did-press-enter-p finished-printing-p))
(if (cdr dialogue-list) (if (cdr dialogue-list)
(list :dialogue (cdr dialogue-list) :map map) (list :dialogue (cdr dialogue-list) :map map)
(progn (progn
(āœŽ:hide-cursor) (āœŽ:hide-cursor)
(values nil (values nil
(list :map map))))) (list :map map)))))
((and did-press-enter-p (not finished-printing-p))
(setf (getf (car dialogue-list) :progress) (length text))
(list :dialogue dialogue-list :map map))
((cdr dialogue-list) ((cdr dialogue-list)
(list :dialogue dialogue-list :map map))))) (list :dialogue dialogue-list :map map)))))
@ -131,7 +150,13 @@ should be printed on the screen at any given moment."
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
;;; Dialogue drawing ;;; Dialogue drawing
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
(defun optimal-speech-layout-horizontally (text coords &key (rightp nil) (width 72) (height 20)) (defun optimal-text-placement-horizontally (text coords &key (rightp nil) (width 72) (height 20))
"Given a horizontal direction (RIGHTP defined or nil) and a focal point COORDS,
return the parameters of a text-box that can optimally fit the given TEXT in the
direction specified relative to the focal point. If a legible position canā€™t be
found, just give up! Return nil.
Otherwise, return a list list with the coordinates, max column, and max row ā€” for
use with RENDER-STRING."
(let* ((text-x-margin (if rightp (let* ((text-x-margin (if rightp
(+ (getf coords :x) 3) (+ (getf coords :x) 3)
0)) 0))
@ -158,7 +183,12 @@ should be printed on the screen at any given moment."
height))))) ;; Max row height))))) ;; Max row
(defun optimal-speech-layout-vertical (text coords &key (downp nil) (width 72) (height 20)) (defun optimal-text-placement-vertically (text coords &key (downp nil) (width 72) (height 20))
"Given a vertical direction (DOWNP defined or nil) and a focal point COORDS,)
return the parameters of a text-box that can optimally fit the given TEXT in the
direction specified relative to the focal point. Return nil if no such placement
is found, otherwise return a list of the coordinates, max-column, and max-row
(for use with RENDER-STRING)."
(let* ((text-y-margin (if downp (let* ((text-y-margin (if downp
(+ (getf coords :y) 1) (+ (getf coords :y) 1)
(- (getf coords :y) 2))) (- (getf coords :y) 2)))
@ -186,25 +216,34 @@ should be printed on the screen at any given moment."
(defun optimal-speech-layout (map dialogue &key (width 72) (height 20)) (defun optimal-speech-layout (map dialogue &key (width 72) (height 20))
"Given a line of DIALOGUE and MAP data, return the ideal ā€œtext-boxā€ for the
text. This tries to place the text on the screen without covering up anything
important, if possible.
The data returned is a list of the boxā€™es top-left coordinate, max-column,
and max-row; for use with RENDER-STRING. Like so:
((:x X :y Y) MAX-COLUMN MAX-ROW)"
(let* ((speaker-id (dialogue-speaker dialogue)) (let* ((speaker-id (dialogue-speaker dialogue))
(direction (šŸŒ:getf-entity-data map speaker-id :direction)) (direction (šŸŒ:getf-entity-data map speaker-id :direction))
(playerp (eq speaker-id 'player)) (playerp (eq speaker-id 'player))
(leftp (not (eq direction 'šŸŒ:right))) (leftp (not (eq direction 'šŸŒ:right)))
(text (getf dialogue :text)) (text (getf dialogue :text))
(coords (šŸŒ:world-coords->screen-coords (šŸŒ:getf-entity-data map speaker-id :coords)))) (coords (šŸŒ:world-coords->screen-coords (šŸŒ:getf-entity-data map speaker-id :coords))))
(format *error-output* "AAA ~A - ~A - ~A" leftp direction 'RIGHT) ;; Ideally, place text-box behind the speaker; otherwise, place it above (NPC) or below (player).
(or (optimal-speech-layout-horizontally text coords :width width :height height (or (optimal-text-placement-horizontally text coords :width width :height height
:rightp leftp) :rightp leftp)
(optimal-speech-layout-vertical text coords :width width :height height (optimal-text-placement-vertically text coords :width width :height height
:downp playerp) :downp playerp)
(optimal-speech-layout-horizontally text coords :width width :height height ;; ā€¦ Worst-case scenario, just do whateverā€™ll fit :w:ā€
:rightp (not leftp)) (optimal-text-placement-vertically text coords :width width :height height
(optimal-speech-layout-vertical text coords :width width :height height :downp (not playerp))
:downp (not playerp))))) (optimal-text-palcement-horizontally text coords :width width :height height
:rightp (not leftp)))))
(defun render-dialogue-block (matrix map dialogue) (defun render-dialogue-block (matrix map dialogue)
"Render a bit of DIALOGUE to the MATRIX, in an intelligent fashion; that is,
make it pretty, dang it! >O<
ā˜†:.ļ½”.o(ā‰§ā–½ā‰¦)o.ļ½”.:ā˜†"
(let* ((progress (getf dialogue :progress)) (let* ((progress (getf dialogue :progress))
(text (getf dialogue :text)) (text (getf dialogue :text))
(optimal-layout (when text (optimal-speech-layout map dialogue)))) (optimal-layout (when text (optimal-speech-layout map dialogue))))
@ -229,13 +268,13 @@ Helper function for DIALOGUE-STATE."
;;; Dialogue loop ;;; Dialogue loop
;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€” ;;; ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”ā€”
(defun dialogue-state (matrix &key dialogue map) (defun dialogue-state (matrix &key dialogue map)
"Render a bit of dialogue to the screen, using :FLORA-SEARCH-AURORA.OVERWORLD "Render a bit of DIALOGUE to the screen, using :FLORA-SEARCH-AURORA.OVERWORLD
entities as the speakers. Dialogue should be in the format: entities as the speakers. Dialogue should be in the format:
((:text \"Hello, papa!\" ((:text \"Hello, papa!\"
:speaker \"son\" ;; The entityā€™s ID (if applicable) :speaker \"son\" ;; The entityā€™s ID (if applicable)
:face \"owo\") ;; If you want their face to change :face \"owo\") ;; If you want their face to change
(:face \"=w=\" :speaker 'son) ;; change their face back when done talking (:face \"=w=\" :speaker 'son) ;; change their face back when done talking
(:text \"Hello, you little gremlin! <3\" (:text \"My dearest son, itā€™s been so long~!\"
:speaker \"papa\" :speaker \"papa\"
...)) ...))
A state-function for use with STATE-LOOP." A state-function for use with STATE-LOOP."

View File

@ -24,6 +24,7 @@
(:export #:overworld-state #:overworld-state-draw (:export #:overworld-state #:overworld-state-draw
#:world-coords->screen-coords #:world-coords->screen-coords
#:getf-entity #:getf-entity-data #:getf-entity #:getf-entity-data
:left :right
:player)) :player))
(in-package :flora-search-aurora.overworld) (in-package :flora-search-aurora.overworld)