/
cells.lisp
354 lines (287 loc) · 12.4 KB
/
cells.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
;;; cells.lisp --- defining objects
;; Copyright (C) 2008 David O'Toole
;; Author: David O'Toole <dto@gnu.org>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(in-package :iosketch)
;;; Base cell prototype
;; This is a base object for forms-browseable objects. See forms.lisp
(define-prototype cell ()
(row :documentation "When non-nil, the current row location of the cell.")
(column :documentation "When non-nil, the current column of the cell.")
(type :initform :cell)
(name :initform nil :documentation "The name of this cell.")
(description :initform nil :documentation "A description of the cell.")
(categories :initform nil :documentation "List of category keyword symbols.")
(label :initform nil :documentation "Optional string or formatted line to display.")
(widget :initform nil)
(image :initform nil :documentation "Image to display. either a resource name string, or an IOSKETCH image object.")
(tile :initform ".asterisk" :documentation "Resource name of image.
When nil, the method DRAW is invoked instead of using a tile.")
(render-cell :initform nil :documentation "Subcell to render. See load-sprite-sheet-resource.")
(excluded-fields :initform '(:widget))
(auto-loadout :initform nil :documentation "When non-nil, the :loadout method is invoked upon entry into a world.")
(auto-deepcopy :initform nil)
(menu :initform nil :documentation "Menu objects."))
(define-method in-category cell (category)
(member category <categories>))
(defparameter *default-cell-width* 16)
(define-method get cell ())
(define-method activate cell ())
(define-method set cell (data))
(define-method print cell () "")
(define-method width cell ()
(with-field-values (widget image label) self
(cond (widget (image-width (field-value :image widget)))
(image (image-width image))
(label (formatted-line-width label))
(t *default-cell-width*))))
(define-method height cell ()
(with-field-values (widget image label) self
(cond (widget (image-height (field-value :image widget)))
(image (image-height image))
(label (formatted-line-height label))
(t *default-cell-width*))))
(define-method render cell (dest x y width)
(with-field-values (widget image) self
(cond (widget
(/render widget)
(draw-image (field-value :image widget)
x y :destination dest))
;; it's an image
(image
(if (stringp image)
(draw-resource-image image x y :destination dest)
(draw-image image x y :destination dest)))
(<label>
;; we have a formatted line
(let ((label <label>))
(when (listp label)
(let*
((shortfall (- width (formatted-line-width label)))
(color (or (when (and (listp label)
(listp (last label)))
(getf (cdr (car (last label))) :background))
".black"))
(spacer (when (plusp shortfall)
(list nil :width shortfall :background color)))
(line (if spacer (append label (list spacer))
label)))
(render-formatted-line line x y :destination dest))))))))
(defvar *default-cell-label* '((" DEF ")))
(define-method get-label cell ()
(when label
(etypecase label
(string (list (list label)))
(list label))))
(define-method set-location cell (r c)
"Set the row R and column C of the cell."
(setf <row> r <column> c))
(define-method is-located cell ()
"Returns non-nil if this cell is located somewhere on the grid."
(or (and (integerp <row>) (integerp <column>))))
(define-method dislocate cell ()
"Remove any location data from the cell."
(when (integerp <row>)
(setf <row> nil <column> nil))
(when (integerp <x>)
(setf <x> nil <y> nil)))
(define-method viewport-coordinates cell ()
"Return as values X,Y the world coordinates of CELL."
(assert (and <row> <column>))
(/get-viewport-coordinates (field-value :viewport *world*)
<row> <column>))
(define-method image-coordinates cell ()
"Return as values X,Y the viewport image coordinates of CELL."
(assert (and <row> <column>))
(/get-image-coordinates (field-value :viewport *world*)
<row> <column>))
(define-method screen-coordinates cell ()
"Return as values X,Y the screen coordinates of CELL."
(assert (and <row> <column>))
(/get-screen-coordinates (field-value :viewport *world*)
<row> <column>))
;;; Cell categories
(define-method in-category cell (category)
"Return non-nil if this cell is in the specified CATEGORY.
Cells may be placed into categories that influence their processing by
the engine. The field `<categories>' is a set of keyword symbols; if a
symbol `:foo' is in the list, then the cell is in the category `:foo'.
Although a game built on IOSKETCH can define whatever categories are
needed, certain base categories are built-in and have a fixed
interpretation:
- :actor --- This cell is active and may be controlled by either the
user or the CPU. Only actor cells receive `:run' messages
every turn. Other cells are purely `reactive'. Actor
cells participate in the Action Points system.
- :target --- This cell is susceptible to targeting.
- :proxy --- This cell is a proxy for another cell.
- :drawn --- This cell has a (/draw) method used for custom drawing.
- :proxied --- This cell is an occupant of a proxy.
- :dead --- This cell is no longer receiving run messages.
- :player --- Only one cell (your player avatar) has this category.
- :enemy --- This cell is playing against you.
- :exclusive --- Prevent some objects from stacking. See also the method `drop-cell' in worlds.lisp
- :obstacle --- Blocks movement and causes collisions
- :pushable --- Can be pushed by impacts.
- :ephemeral --- This cell is not preserved when exiting a world.
- :combining --- This cell automatically combines units with other cells in a container.
- :light-source --- This object casts light.
- :opaque --- Blocks line-of-sight, casts shadows.
- :container --- This cell contains other cells, and has an <inventory> field
- :contained --- This cell is contained in another cell (i.e. not in open space on the map)
- :item --- A potential inventory item.
- :equipper --- Uses equipment.
- :equipped --- This item is currently equipped.
- :equipment --- This item can be equipped.
"
(member category <categories>))
(define-method add-category cell (category)
"Add this cell to the specified CATEGORY."
(pushnew category <categories>))
(define-method delete-category cell (category)
"Remove this cell from the specified CATEGORY."
(setf <categories> (remove category <categories>)))
;;; Player orientation
(define-method distance-to-player cell ()
"Calculate the distance from the current location to the player."
;; todo fix for sprites
(multiple-value-bind (row column) (/grid-coordinates self)
(/distance-to-player *world* row column)))
(define-method direction-to-player cell ()
"Calculate the general compass direction of the player."
(/direction-to-player *world* <row> <column>))
(define-method adjacent-to-player cell ()
(/adjacent-to-player *world* <row> <column>))
;;; Convenience macro for defining cells.
(defmacro defcell (name &body args)
"Define a cell named NAME, with the fields ARGS as in a normal
prototype declaration. This is a convenience macro for defining new
cells."
`(define-prototype ,name (:parent =cell=)
,@args))
;;; Cell movement
(define-method move cell (direction &optional (distance 1) unit)
"Move this cell one step in DIRECTION on the grid. If
IGNORE-OBSTACLES is non-nil, the move will occur even if an obstacle
is in the way. Returns non-nil if a move occurred."
(declare (ignore unit))
(let ((world *world*))
(multiple-value-bind (r c)
(step-in-direction <row> <column> direction)
;;
(cond ((null (/grid-location world r c)) ;; are we at the edge?
;; return nil because we didn't move
(prog1 nil
;; edge conditions only affect player for now
(when (/is-player self)
(ecase (field-value :edge-condition world)
(:block (/say self "You cannot move in that direction."))
(:wrap nil) ;; TODO implement this for planet maps
(:exit (/exit *universe*))))))
(t
(when (or ignore-obstacles
(not (/obstacle-at-p *world* r c)))
;; return t because we moved
(prog1 t
;; (/expend-action-points self (/stat-value self :movement-cost))
(/move world self r c))))))))
;; (when <stepping>
;; (/step-on-current-square self)))))))))
(define-method set-location cell (r c)
"Set the row R and column C of the cell."
(setf <row> r <column> c))
(define-method move-to cell (unit r c)
(assert (member unit '(:space :spaces)))
(/delete-cell *world* self <row> <column>)
(/drop-cell *world* self r c))
;;; Adding items to the world
(define-method drop cell (cell &key loadout (exclusive nil))
"Add CELL to the world at the current location. By default,
EXCLUSIVE is nil; this allows one to drop objects on top of oneself.
When LOADOUT is non-nil, call the :loadout method."
(/drop-cell *world* cell <row> <column> :loadout loadout :exclusive exclusive))
(define-method drop-sprite cell (sprite &optional x y)
"Add SPRITE to the world at location X,Y."
(multiple-value-bind (x0 y0)
(/xy-coordinates self)
(let ((x1 (or x x0))
(y1 (or y y0)))
(/add-sprite *world* sprite)
(assert (and x1 y1))
(/move-to sprite x1 y1))))
(define-method is-player cell ()
(/in-category self :player))
;;; Finding and manipulating objects
(define-method find cell (&key (direction :here) (index :top) category)
(let ((world *world*))
(multiple-value-bind (nrow ncol)
(step-in-direction <row> <column> direction)
(if (/in-bounds-p world nrow ncol)
(let (cell)
(let* ((cells (/grid-location world nrow ncol))
(index2 (cond
((not (null category))
(setf cell (/category-at-p world nrow ncol category))
(position cell cells :test 'eq))
((and (eq :top index) (eq :here direction))
;; skip yourself and instead get the item you're standing on
(- (fill-pointer cells) 2))
((eq :top index)
(- (fill-pointer cells) 1))
((numberp index)
(when (array-in-bounds-p cells index)
index)))))
(message "INDEX2: ~A" index2)
(setf cell (aref cells index2))
(values cell nrow ncol index2)))))))
(define-method clear-location cell ()
(setf <row> nil <column> nil))
(define-method delete-from-world cell ()
(/delete-cell *world* self <row> <column>))
(define-method quit cell ()
"Leave the gameworld."
(/delete-from-world self))
;;; Custom rendering
(define-method draw cell (x y image)
"Use IOSKETCH drawing commands to render a presentation of this cell at
X, Y to the offscreen image IMAGE. This method is invoked to draw a
cell when its TILE field is nil, or when it is in the
category :drawn. See also viewport.lisp."
nil)
;;; Loadout
;; Automatic inventory and equipment loadout for new cells.
;; See how this is used in worlds.lisp.
(define-method loadout cell ()
"This is called for cells after being dropped in a world, with a
non-nil :loadout argument. It can also be triggered manually. Use
`loadout' for things that have to be done while in a world. (During
your cell's normal PROTON `initialize' method, the cell will not be in a
world or have a location."
nil)
(define-method start cell ()
"This method is invoked on cells whenever a new world map is visited."
nil)
(define-method exit cell ()
"This method is invoked on a player cell when it leaves a world."
nil)
;;; Collision; see also gsprites.lisp
(define-method do-collision cell (object)
"Respond to a collision detected with OBJECT."
nil)
(define-method grid-coordinates cell ()
(values <row> <column>))
(define-method xy-coordinates cell ()
(values (* <column> (field-value :tile-size *world*))
(* <row> (field-value :tile-size *world*))))
;;; cells.lisp ends here