1 ;;;; Ru*** roLeplay Playground virtual tabletop
2 ;;;; Copyright (C) 2022 Zull
4 ;;;; This program is free software: you can redistribute it and/or modify
5 ;;;; it under the terms of the GNU General Public License as published by
6 ;;;; the Free Software Foundation, either version 3 of the License, or
7 ;;;; (at your option) any later version.
9 ;;;; This program is distributed in the hope that it will be useful,
10 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;;; GNU General Public License for more details.
14 ;;;; You should have received a copy of the GNU General Public License
15 ;;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
17 (in-package :rulp.graphics)
18 #| -------------------------------------------
19 | This file manipulate the window and the
21 | -------------------------------------------
24 (defun create-plane (map-info map-path &key (number 0))
25 "convert the map informations into a functioning plane, without number it convert the
27 (let ((plane-info (nth number (cdr (assoc :planes map-info)))))
29 (make-instance 'rulp.layers:plane
30 :background (merge-pathnames map-path (cdr (assoc :image-path plane-info))))
31 (cdr (assoc :grid-dimension plane-info))
33 ;; FIXME: generalize... somehow
37 ;; FIXME: search alternative
38 (defun create-entities (map-info map-path)
39 (let ((entities-info (cdr (assoc :entities map-info))))
40 (loop :for entity-info :in entities-info
42 (make-instance 'entity
43 :image (merge-pathnames map-path (cdr (assoc :image-path entity-info)))
44 :size (if (assoc :size entity-info) (cdr (assoc :size entity-info)) 1)
45 :name (assoc :name entity-info)
51 (defun grid-layout (x y &key (size 1))
52 "converts natural coordinates and size into pixel positions. This function can be replaced to change
53 the actual grid layout"
54 (values (cons (* x *plane-grid*)
56 (* size *plane-grid*)))
58 (defun actors-layout (x y)
59 (cons (floor (/ x *plane-grid*))
60 (floor (/ y *plane-grid*)))
63 (defmacro with-playground ((window renderer &key (title "RuLP")) &body body)
64 `(sdl2:with-window (,window :title title :w *window-width* :h *window-height* :flags '(:resizable))
65 ;; NOTE: here is where you should set the window icon, if there's the method to do that
66 (sdl2:with-renderer (,renderer ,window :index -1 :flags '(:accelerated :presentvsync)) ;later add delta
67 (sdl2-image:init '(:png :jpg))
74 ;; FIXME: temporary place
75 ;; set boundaries so the plane doesn't go away with the panning. fix boundaries to -window width, -window height, plane width, plane height
76 (defparameter *viewpoint-offset* '(0 . 0))
77 (defparameter *viewpoint-zoom* 1)
78 (defparameter *mouse-previous* '(0 . 0))
80 (defparameter *framerule* 20
81 "every 20 frames the parameter *changep* is set to t, so it updates informations
82 even when away from keyboard")
84 (defparameter *active-entries* nil
85 "contain the active menu in form of a entry class")
87 (defparameter *changep* t
88 "set to t when a change on the plane is made, if nil the viewpoint is not updated")
90 ;; renderer exists only inside this function, so you cannot create a texture outside
91 ;; (at least for now), more on this later os
92 (defun playground (title &optional (fps 60) (debug-info nil))
93 "initialize and run the game loop. it takes a TITLE to display and FPS for framerate.
94 DEBUG-INFO can be used to display the content on screen for test and debug purposes."
95 (declare (ignore fps))
96 (sdl2:with-init (:video)
97 (with-playground (window rulp.render:*renderer* :title title)
98 (setf *tr-texture* (let* ((font (sdl2-ttf:open-font "media/IBMPlex.ttf" 100)) ;; FIXME: this crashes the program under windows, throws error on linux but works anyway
99 (font-surface (sdl2-ttf:render-utf8-solid font *tr-string* 0 0 0 0))
100 (font-texture (sdl2:create-texture-from-surface rulp.render:*renderer* font-surface)))
101 (sdl2:free-surface font-surface)
103 (multiple-value-bind (p g) (create-plane *map-info* *map-path*)
105 (setf *plane-grid* g))
106 (setf *entities-list* (create-entities *map-info* *map-path*))
107 (let (;; (mouse-button-previous nil)
108 (window-texture (sdl2:get-render-target rulp.render:*renderer*))
109 (viewpoint-texture (sdl2:create-texture rulp.render:*renderer* (sdl2:get-window-pixel-format window)
110 2 (width *plane*) (height *plane*))) ;camping into the ramhog
111 (viewpoint-rectangle (sdl2:make-rect 0 0 10 10)))
112 (sdl2:with-event-loop (:method :poll)
115 ;; FIXME: incorporate into input.lisp. check for input every now and then outside keydown
116 (when (sdl2:keyboard-state-p :scancode-up) (setf (cdr *viewpoint-offset*) (+ (cdr *viewpoint-offset*) 10)))
117 (when (sdl2:keyboard-state-p :scancode-down) (setf (cdr *viewpoint-offset*) (+ (cdr *viewpoint-offset*) -10)))
118 (when (sdl2:keyboard-state-p :scancode-left) (setf (car *viewpoint-offset*) (+ (car *viewpoint-offset*) 10)))
119 (when (sdl2:keyboard-state-p :scancode-right) (setf (car *viewpoint-offset*) (+ (car *viewpoint-offset*) -10)))
120 (when (sdl2:keyboard-state-p :scancode-p) (setf *viewpoint-zoom* (+ *viewpoint-zoom* 0.2))) ; FIXME: find the scancode for the plus sign
121 (when (sdl2:keyboard-state-p :scancode-m) (setf *viewpoint-zoom* (+ *viewpoint-zoom* -0.2)))
124 (setf *is-mouse-hold* t)
125 ;; this routine seems identical to the one in idle, instead
126 ;; of calling the second element of the keybind list it calls
127 ;; the third element. If there is something there it would
128 ;; execute a special function for when the mouse is released.
129 (mouse-event *mouse-position*)
132 ;; what about functions that require a single press? just create
133 ;; a keybind (+key+ nil (...)) and it will execute just on
134 ;; release and not on hold
135 ;; (setf mouse-button-previous nil)
137 (setf *is-mouse-hold* nil)
140 ;; bug prevention method. SDL2 can look the :mousebuttonup event
141 ;; only when the mouse is on the window. This makes press,
142 ;; alt+tab and release a way to keep the value to t. This
143 ;; event is a prevention method designed to release the hold
144 ;; when alt+tab or other window shortcuts are used.
145 (setf *is-mouse-hold* nil))
147 ;; same as for windowevent
148 (setf *is-mouse-hold* nil))
149 ;; NOTE: When the user prefear to use only the keyboard or joystick
150 ;; the *cursor-position* is set default, this is set when a key or
151 ;; koystick button is pressed, then menues and actions are chosed with
152 ;; the cursor position. When a mouse motion is detected the
153 ;; mouse-position is set default. the cursor follows the grid on each
154 ;; step and it is an alternative to the mouse movement to do
157 (setf *mouse-previous-position* *mouse-position*)
158 (multiple-value-bind (x y) (sdl2:mouse-state)
159 (setf *mouse-position* (cons x y)))
160 ;; mouse-holding-event
161 ;; (unless *active-entries*
162 ;; (when *is-mouse-hold*
163 ;; (loop :for action :in (mouse-actions *mouse-keybinds*)
164 ;; :do (when action (eval (car action))) ; FIXME: replace eval with a more safer DSL eval
166 ;; ;; (setf mouse-button-previous (mouse-actions *mouse-keybinds*))
168 (when *is-mouse-hold*
169 (mouse-event *mouse-position* :dragp t))
171 ;; trick to avoid functions to change the global draw-color
172 (sdl2:set-render-draw-color rulp.render:*renderer* 0 0 0 255)
177 (sdl2:destroy-texture viewpoint-texture)
178 (setf viewpoint-texture (sdl2:create-texture rulp.render:*renderer*
179 (sdl2:get-window-pixel-format window)
183 (sdl2:set-render-target rulp.render:*renderer* viewpoint-texture)
184 (sdl2:render-clear rulp.render:*renderer*)
185 (render-plane-and-entities rulp.render:*renderer*)
187 (grid-render rulp.render:*renderer* 0 0 (width *plane*) (height *plane*))
190 (indexes-render rulp.render:*renderer* 0 0 (width *plane*) (height *plane*)))
191 (sdl2:set-render-draw-color rulp.render:*renderer* 0 0 0 255)
194 (when *pointer* ;; NOTE: to test this out
195 (sdl2:set-render-draw-color rulp.render:*renderer* 128 250 33 255)
196 (multiple-value-bind (coordinates size) (grid-layout (car (coordinate (nth *pointer* *entities-list*)))
197 (cdr (coordinate (nth *pointer* *entities-list*)))
198 :size (size (nth *pointer* *entities-list*)))
199 (let ((select-rectangle (sdl2:make-rect (car coordinates) (cdr coordinates) size size)))
200 (sdl2:render-draw-rect rulp.render:*renderer* select-rectangle)
201 (sdl2:free-rect select-rectangle)))
202 (sdl2:set-render-draw-color rulp.render:*renderer* 0 0 0 255)
207 ;; display viewpoint on window
208 (sdl2:set-render-target rulp.render:*renderer* window-texture)
209 (sdl2:render-clear rulp.render:*renderer*)
210 (setf (sdl2:rect-x viewpoint-rectangle) (car *viewpoint-offset*))
211 (setf (sdl2:rect-y viewpoint-rectangle) (cdr *viewpoint-offset*))
212 (setf (sdl2:rect-width viewpoint-rectangle) (floor (* (width *plane*) *viewpoint-zoom*)))
213 (setf (sdl2:rect-height viewpoint-rectangle) (floor (* (height *plane*) *viewpoint-zoom*)))
214 (sdl2:render-copy rulp.render:*renderer* viewpoint-texture
216 :dest-rect viewpoint-rectangle)
218 ;; entries visualization
219 (loop :for entry :in rulp.entries:*entries-list*
220 :do (rulp.entries:render-entry rulp.render:*renderer* entry))
221 ;; (loop :for entry :in *active-entries*
222 ;; :do (display-entry rulp.render:*renderer* entry))
226 (tr-write (format nil "~A" debug-info) 0 0 10 15 rulp.render:*renderer*))
227 (sdl2:render-present rulp.render:*renderer*)
228 ;; updating grids and dimension
229 ;; FIXME: i hate this, find a better way to dinamically change the window dimension
230 (multiple-value-bind (new-width new-height) (sdl2:get-window-size window)
231 (setf *window-width* new-width)
232 (setf *window-height* new-height))
234 (when (< *framerule* 1)
236 (setf *framerule* 20))))
237 (setf *changep* t) ; useful in the repl where parameters are not reset
239 ;; sdl2 kills all textures when the session ends. on normal execution this
240 ;; is not a problem but on emacs because the editor will not delete its
241 ;; variables. Therefore the flyweight would be filled with screens with no
242 ;; textures that will fail the second execution.
243 (rulp.layers:empty-screen-list)