1 /*
2 Copyright (c) 2009 Peter "Corsix" Cawley
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 of the Software, and to permit persons to whom the Software is furnished to do
9 so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 */
22 
23 #ifndef CORSIX_TH_TH_GFX_H_
24 #define CORSIX_TH_TH_GFX_H_
25 #include <map>
26 #include <string>
27 #include <vector>
28 
29 #include "th.h"
30 #include "th_gfx_sdl.h"
31 
32 class lua_persist_reader;
33 class lua_persist_writer;
34 
35 enum class scaled_items { none, sprite_sheets, bitmaps, all };
36 
37 void clip_rect_intersection(clip_rect& rcClip, const clip_rect& rcIntersect);
38 
39 //! Bitflags for drawing operations
40 enum draw_flags : uint32_t {
41   /** Sprite drawing flags **/
42   /* Where possible, designed to be the same values used by TH data files */
43 
44   //! Draw with the left becoming the right and vice versa
45   thdf_flip_horizontal = 1 << 0,
46   //! Draw with the top becoming the bottom and vice versa
47   thdf_flip_vertical = 1 << 1,
48   //! Draw with 50% transparency
49   thdf_alpha_50 = 1 << 2,
50   //! Draw with 75% transparency
51   thdf_alpha_75 = 1 << 3,
52   //! Draw using a remapped palette
53   thdf_alt_palette = 1 << 4,
54 
55   /** How to draw alternative palette in 32bpp. */
56   /* A 3 bit field (bits 5,6,7), currently 2 bits used. */
57 
58   //! Lowest bit of the field.
59   thdf_alt32_start = 5,
60   //! Mask for the 32bpp alternative drawing values.
61   thdf_alt32_mask = 0x7 << thdf_alt32_start,
62 
63   //! Draw the sprite with the normal palette (fallback option).
64   thdf_alt32_plain = 0 << thdf_alt32_start,
65   //! Draw the sprite in grey scale.
66   thdf_alt32_grey_scale = 1 << thdf_alt32_start,
67   //! Draw the sprite with red and blue colours swapped.
68   thdf_alt32_blue_red_swap = 2 << thdf_alt32_start,
69 
70   /** Object attached to tile flags **/
71   /* (should be set prior to attaching to a tile) */
72 
73   //! Attach to the early sprite list (right-to-left pass)
74   thdf_early_list = 1 << 10,
75   //! Keep this sprite at the bottom of the attached list
76   thdf_list_bottom = 1 << 11,
77   //! Hit-test using bounding-box precision rather than pixel-perfect
78   thdf_bound_box_hit_test = 1 << 12,
79   //! Apply a cropping operation prior to drawing
80   thdf_crop = 1 << 13,
81 };
82 
83 /** Helper structure with parameters to create a #render_target. */
84 struct render_target_creation_params {
85   int width;               ///< Expected width of the render target.
86   int height;              ///< Expected height of the render target.
87   int bpp;                 ///< Expected colour depth of the render target.
88   bool fullscreen;         ///< Run full-screen.
89   bool present_immediate;  ///< Whether to present immediately to the user
90                            ///< (else wait for Vsync).
91   bool direct_zoom;  ///< Scale each texture when copying if true, otherwise
92                      ///< render to intermediate texture and scale.
93 };
94 
95 /*!
96     Base class for a linked list of drawable objects.
97     Note that "object" is used as a generic term, not in specific reference to
98     game objects (though they are the most common thing in drawing lists).
99 */
100 // TODO: Replace this struct with something cleaner
101 struct drawable : public link_list {
drawabledrawable102   drawable() : link_list() { drawing_layer = 0; }
103 
104   //! Draw the object at a specific point on a render target
105   /*!
106       Can also "draw" the object to the speakers, i.e. play sounds.
107   */
108   void (*draw_fn)(drawable* pSelf, render_target* pCanvas, int iDestX,
109                   int iDestY);
110 
111   //! Perform a hit test against the object
112   /*!
113       Should return true if when the object is drawn at (iDestX, iDestY) on a
114      canvas, the point (iTestX, iTestY) is within / on the object.
115   */
116   bool (*hit_test_fn)(drawable* pSelf, int iDestX, int iDestY, int iTestX,
117                       int iTestY);
118 
119   //! Drawing flags (zero or more list flags from #draw_flags).
120   uint32_t flags;
121 
122   /** Returns true if instance is a multiple frame animation.
123       Should be overloaded in derived class.
124   */
125   bool (*is_multiple_frame_animation_fn)(drawable* pSelf);
126 
get_drawing_layerdrawable127   int get_drawing_layer() { return drawing_layer; }
set_drawing_layerdrawable128   void set_drawing_layer(int layer) { drawing_layer = layer; }
129 
130  private:
131   int drawing_layer;
132 };
133 
134 /*!
135     Utility class for decoding Theme Hospital "chunked" graphics files.
136     Generally used internally by sprite_sheet.
137 */
138 class chunk_renderer {
139  public:
140   //! Initialise a renderer for a specific size result
141   /*!
142       @param width Pixel width of the resulting image
143       @param height Pixel height of the resulting image
144       @param buffer If nullptr, then a new buffer is created to render the
145      image onto. Otherwise, should be an array at least width*height in size.
146         Ownership of this pointer is assumed by the class - call takeData()
147         to take ownership back again.
148   */
149   chunk_renderer(int width, int height, uint8_t* buffer = nullptr);
150 
151   ~chunk_renderer();
152 
153   // TODO: Should be function, not method of chunk_renderer
154   //! Convert a stream of chunks into a raw bitmap
155   /*!
156       @param pData Stream data.
157       @param iDataLen Length of \a pData.
158       @param bComplex true if pData is a stream of "complex" chunks, false if
159         pData is a stream of "simple" chunks. Passing the wrong value will
160         usually result in a very visible wrong result.
161 
162       Use getData() or takeData() to obtain the resulting bitmap.
163   */
164   void decode_chunks(const uint8_t* pData, int iDataLen, bool bComplex);
165 
166   //! Get the result buffer, and take ownership of it
167   /*!
168       This transfers ownership of the buffer to the caller. After calling,
169       the class will not have any buffer, and thus cannot be used for
170       anything.
171   */
172   uint8_t* take_data();
173 
174   //! Get the result buffer
get_data()175   inline const uint8_t* get_data() const { return data; }
176 
177   //! Perform a "copy" chunk (normally called by decodeChunks)
178   void chunk_copy(int npixels, const uint8_t* in_data);
179 
180   //! Perform a "fill" chunk (normally called by decodeChunks)
181   void chunk_fill(int npixels, uint8_t value);
182 
183   //! Perform a "fill to end of line" chunk (normally called by decodeChunks)
184   void chunk_fill_to_end_of_line(uint8_t value);
185 
186   //! Perform a "fill to end of file" chunk (normally called by decodeChunks)
187   void chunk_finish(uint8_t value);
188 
189  private:
is_done()190   inline bool is_done() { return ptr == end; }
191   inline void fix_n_pixels(int& npixels) const;
192   inline void increment_position(int npixels);
193 
194   uint8_t *data, *ptr, *end;
195   int x, y, width, height;
196   bool skip_eol;
197 };
198 
199 //! Layer information (see animation_manager::draw_frame)
200 struct layers {
201   uint8_t layer_contents[13];
202 };
203 
204 class memory_reader;
205 
206 /** Key value for finding an animation. */
207 struct animation_key {
208   std::string name;  ///< Name of the animations.
209   int tile_size;     ///< Size of a tile.
210 };
211 
212 //! Less-than operator for map-sorting.
213 /*!
214     @param oK First key value.
215     @param oL Second key value.
216     @return Whether \a oK should be before \a oL.
217  */
218 inline bool operator<(const animation_key& oK, const animation_key& oL) {
219   if (oK.tile_size != oL.tile_size) return oK.tile_size < oL.tile_size;
220   return oK.name < oL.name;
221 }
222 
223 /**
224  * Start frames of an animation, in each view direction.
225  * A negative number indicates there is no animation in that direction.
226  */
227 struct animation_start_frames {
228   long north;  ///< Animation start frame for the 'north' view.
229   long east;   ///< Animation start frame for the 'east' view.
230   long south;  ///< Animation start frame for the 'south' view.
231   long west;   ///< Animation start frame for the 'west' view.
232 };
233 
234 /** Map holding the custom animations. */
235 typedef std::map<animation_key, animation_start_frames> named_animations_map;
236 
237 /** Insertion data structure. */
238 typedef std::pair<animation_key, animation_start_frames> named_animation_pair;
239 
240 //! Theme Hospital sprite animation manager
241 /*!
242     An animation manager takes a sprite sheet and four animation information
243     files, and uses them to draw animation frames and provide information about
244     the animations.
245 */
246 class animation_manager {
247  public:
248   animation_manager();
249   ~animation_manager();
250 
251   void set_sprite_sheet(sprite_sheet* pSpriteSheet);
252 
253   //! Load original animations.
254   /*!
255       set_sprite_sheet() must be called before calling this.
256       @param pStartData Animation first frame indices (e.g. VSTART-1.ANI)
257       @param iStartDataLength Length of \a pStartData.
258       @param pFrameData Frame details (e.g. VFRA-1.ANI)
259       @param iFrameDataLength Length of \a pFrameData
260       @param pListData Element indices list (e.g. VLIST-1.ANI)
261       @param iListDataLength Length of \a pListData
262       @param pElementData Element details (e.g. VELE-1.ANI)
263       @param iElementDataLength Length of \a pElementData
264       @return Loading was successful.
265   */
266   bool load_from_th_file(const uint8_t* pStartData, size_t iStartDataLength,
267                          const uint8_t* pFrameData, size_t iFrameDataLength,
268                          const uint8_t* pListData, size_t iListDataLength,
269                          const uint8_t* pElementData,
270                          size_t iElementDataLength);
271 
272   //! Set the video target.
273   /*!
274      @param pCanvas Video surface to use.
275    */
276   void set_canvas(render_target* pCanvas);
277 
278   //! Load free animations.
279   /*!
280       @param pData Start of the loaded data.
281       @param iDataLength Length of the loaded data.
282       @return Loading was successful.
283   */
284   bool load_custom_animations(const uint8_t* pData, size_t iDataLength);
285 
286   //! Get the total number of animations
287   size_t get_animation_count() const;
288 
289   //! Get the total number of animation frames
290   size_t get_frame_count() const;
291 
292   //! Get the index of the first frame of an animation
293   size_t get_first_frame(size_t iAnimation) const;
294 
295   //! Get the index of the frame after a given frame
296   /*!
297       To draw an animation frame by frame, call get_first_frame() to get the
298       index of the first frame, and then keep on calling get_next_frame()
299      using the most recent return value from get_next_frame() or
300      get_first_frame().
301   */
302   size_t get_next_frame(size_t iFrame) const;
303 
304   //! Set the palette remap data for an animation
305   /*!
306       This sets the palette remap data for every single sprite used by the
307       given animation. If the animation (or any of its sprites) are drawn
308       using the thdf_alt_palette flag, then palette indices will be mapped to
309       new palette indices by the 256 byte array pMap. This is typically used
310       to draw things in different colours or in greyscale.
311   */
312   void set_animation_alt_palette_map(size_t iAnimation, const uint8_t* pMap,
313                                      uint32_t iAlt32);
314 
315   //! Draw an animation frame
316   /*!
317       @param pCanvas The render target to draw onto.
318       @param iFrame The frame index to draw (should be in range [0,
319      getFrameCount() - 1])
320       @param oLayers Information to decide what to draw on each layer.
321           An animation is comprised of up to thirteen layers, numbered 0
322           through 12. Some animations will have different options for what to
323           render on each layer. For example, patient animations generally
324           have the different options on layer 1 as different clothes, so if
325           layer 1 is set to the value 0, they may have their default clothes,
326           and if set to the value 2 or 4 or 6, they may have other clothes.
327           Play with the AnimView tool for a better understanding of layers,
328           though note that while it can draw more than one option on each
329           layer, this class can only draw a single option for each layer.
330       @param iX The screen position to use as the animation X origin.
331       @param iY The screen position to use as the animation Y origin.
332       @param iFlags Zero or more THDrawFlags flags.
333   */
334   void draw_frame(render_target* pCanvas, size_t iFrame,
335                   const ::layers& oLayers, int iX, int iY,
336                   uint32_t iFlags) const;
337 
338   void get_frame_extent(size_t iFrame, const ::layers& oLayers, int* pMinX,
339                         int* pMaxX, int* pMinY, int* pMaxY,
340                         uint32_t iFlags) const;
341   size_t get_frame_sound(size_t iFrame);
342 
343   bool hit_test(size_t iFrame, const ::layers& oLayers, int iX, int iY,
344                 uint32_t iFlags, int iTestX, int iTestY) const;
345 
346   bool set_frame_marker(size_t iFrame, int iX, int iY);
347   bool set_frame_secondary_marker(size_t iFrame, int iX, int iY);
348   bool get_frame_marker(size_t iFrame, int* pX, int* pY);
349   bool get_frame_secondary_marker(size_t iFrame, int* pX, int* pY);
350 
351   //! Retrieve a custom animation by name and tile size.
352   /*!
353       @param sName Name of the animation.
354       @param iTilesize Tile size of the animation.
355       @return A set starting frames for the queried animation.
356    */
357   const animation_start_frames& get_named_animations(const std::string& sName,
358                                                      int iTilesize) const;
359 
360  private:
361 #if CORSIX_TH_USE_PACK_PRAGMAS
362 #pragma pack(push)
363 #pragma pack(1)
364 #endif
365   // Animation information structure reinterpreted from Theme Hospital data.
366   struct th_animation_properties {
367     uint16_t first_frame;
368     // It could be that frame is a uint32_t rather than a uint16_t, which
369     // would resolve the following unknown (which seems to always be zero).
370     uint16_t unknown;
371   } CORSIX_TH_PACKED_FLAGS;
372 
373   // Frame information structure reinterpreted from Theme Hospital data.
374   struct th_frame_properties {
375     uint32_t list_index;
376     // These fields have something to do with width and height, but it's
377     // not clear quite exactly how.
378     uint8_t width;
379     uint8_t height;
380     // If non-zero, index into sound.dat filetable.
381     uint8_t sound;
382     // Combination of zero or more fame_flags values
383     uint8_t flags;
384     uint16_t next;
385   } CORSIX_TH_PACKED_FLAGS;
386 
387   // Structure reinterpreted from Theme Hospital data.
388   struct th_element_properties {
389     uint16_t table_position;
390     uint8_t offx;
391     uint8_t offy;
392     // High nibble: The layer which the element belongs to [0, 12]
393     // Low  nibble: Zero or more draw_flags
394     uint8_t flags;
395     // The layer option / layer id
396     uint8_t layerid;
397   } CORSIX_TH_PACKED_FLAGS;
398 #if CORSIX_TH_USE_PACK_PRAGMAS
399 #pragma pack(pop)
400 #endif
401 
402   struct frame {
403     size_t list_index;   ///< First entry in #element_list (pointing to an
404                          ///< element) for this frame.
405     size_t next_frame;   ///< Number of the next frame.
406     unsigned int sound;  ///< Sound to play, if non-zero.
407     unsigned int flags;  ///< Flags of the frame. Bit 0=start of animation.
408 
409     // Bounding rectangle is with all layers / options enabled - used as a
410     // quick test prior to a full pixel perfect test.
411     int bounding_left;    ///< Left edge of the bounding rectangle of this
412                           ///< frame.
413     int bounding_right;   ///< Right edge of the bounding rectangle of this
414                           ///< frame.
415     int bounding_top;     ///< Top edge of the bounding rectangle of this
416                           ///< frame.
417     int bounding_bottom;  ///< Bottom edge of the bounding rectangle of this
418                           ///< frame.
419 
420     // Markers are used to know where humanoids are on an frame. The
421     // positions are pixels offsets from the centre of the frame's base
422     // tile to the centre of the humanoid's feet.
423     int marker_x;  ///< X position of the first center of a humanoids feet.
424     int marker_y;  ///< Y position of the first center of a humanoids feet.
425     int secondary_marker_x;  ///< X position of the second center of a
426                              ///< humanoids feet.
427     int secondary_marker_y;  ///< Y position of the second center of a
428                              ///< humanoids feet.
429   };
430 
431   struct element {
432     size_t sprite;     ///< Sprite number of the sprite sheet to display.
433     uint32_t flags;    ///< Flags of the sprite.
434                        ///< bit 0=flip vertically, bit 1=flip horizontally,
435                        ///< bit 2=draw 50% alpha, bit 3=draw 75% alpha.
436     int x;             ///< X offset of the sprite.
437     int y;             ///< Y offset of the sprite.
438     uint8_t layer;     ///< Layer class (0..12).
439     uint8_t layer_id;  ///< Value of the layer class to match.
440 
441     sprite_sheet* element_sprite_sheet;  ///< Sprite sheet to use for this
442                                          ///< element.
443   };
444 
445   std::vector<size_t> first_frames;    ///< First frame number of an animation.
446   std::vector<frame> frames;           ///< The loaded frames.
447   std::vector<uint16_t> element_list;  ///< List of elements for a frame.
448   std::vector<element> elements;       ///< Sprite Elements.
449   std::vector<sprite_sheet*>
450       custom_sheets;  ///< Sprite sheets with custom graphics.
451   named_animations_map named_animations;  ///< Collected named animations.
452 
453   sprite_sheet* sheet;    ///< Sprite sheet to use.
454   render_target* canvas;  ///< Video surface to use.
455 
456   size_t animation_count;     ///< Number of animations.
457   size_t frame_count;         ///< Number of frames.
458   size_t element_list_count;  ///< Number of list elements.
459   size_t element_count;       ///< Number of sprite elements.
460 
461   //! Compute the bounding box of the frame.
462   /*!
463       @param oFrame Frame to inspect/set.
464    */
465   void set_bounding_box(frame& oFrame);
466 
467   //! Load sprite elements from the input.
468   /*!
469       @param [inout] input Data to read.
470       @param pSpriteSheet Sprite sheet to use.
471       @param iNumElements Number of elements to read.
472       @param [inout] iLoadedElements Number of loaded elements so far.
473       @param iElementStart Offset of the first element.
474       @param iElementCount Number of elements to load.
475       @return Index of the first loaded element in #elements. Negative value
476      means failure.
477    */
478   size_t load_elements(memory_reader& input, sprite_sheet* pSpriteSheet,
479                        size_t iNumElements, size_t& iLoadedElements,
480                        size_t iElementStart, size_t iElementCount);
481 
482   //! Construct a list element for every element, and a 0xFFFF at the end.
483   /*!
484       @param iFirstElement Index of the first element in #elements.
485       @param iNumElements Number of elements to add.
486       @param [inout] iLoadedListElements Number of created list elements so
487      far.
488       @param iListStart Offset of the first created list element.
489       @param iListCount Expected number of list elements to create.
490       @return Index of the list elements, or a negative value to indicate
491      failure.
492    */
493   size_t make_list_elements(size_t iFirstElement, size_t iNumElements,
494                             size_t& iLoadedListElements, size_t iListStart,
495                             size_t iListCount);
496 
497   //! Fix the flags of the first frame, and set the next frame of the last
498   //! frame back to the first frame.
499   /*!
500       @param iFirst First frame of the animation, or 0xFFFFFFFFu.
501       @param iLength Number of frames in the animation.
502    */
503   void fix_next_frame(uint32_t iFirst, size_t iLength);
504 };
505 
506 struct map_tile;
507 class animation_base : public drawable {
508  public:
509   animation_base();
510 
511   void remove_from_tile();
512   void attach_to_tile(map_tile* pMapNode, int layer);
513 
get_flags()514   uint32_t get_flags() const { return flags; }
get_x()515   int get_x() const { return x_relative_to_tile; }
get_y()516   int get_y() const { return y_relative_to_tile; }
517 
set_flags(uint32_t iFlags)518   void set_flags(uint32_t iFlags) { flags = iFlags; }
set_position(int iX,int iY)519   void set_position(int iX, int iY) {
520     x_relative_to_tile = iX, y_relative_to_tile = iY;
521   }
522   void set_layer(int iLayer, int iId);
set_layers_from(const animation_base * pSrc)523   void set_layers_from(const animation_base* pSrc) { layers = pSrc->layers; }
524 
525   // bool isMultipleFrameAnimation() { return false;}
526  protected:
527   //! X position on tile (not tile x-index)
528   int x_relative_to_tile;
529   //! Y position on tile (not tile y-index)
530   int y_relative_to_tile;
531 
532   ::layers layers;
533 };
534 
535 struct xy_diff {
536   //! Amount to change x per tick
537   int dx;
538   //! Amount to change y per tick
539   int dy;
540 };
541 
542 class animation : public animation_base {
543  public:
544   animation();
545 
546   void set_parent(animation* pParent);
547 
548   void tick();
549   void draw(render_target* pCanvas, int iDestX, int iDestY);
550   bool hit_test(int iDestX, int iDestY, int iTestX, int iTestY);
551   void draw_morph(render_target* pCanvas, int iDestX, int iDestY);
552   bool hit_test_morph(int iDestX, int iDestY, int iTestX, int iTestY);
553   void draw_child(render_target* pCanvas, int iDestX, int iDestY);
554   bool hit_test_child(int iDestX, int iDestY, int iTestX, int iTestY);
555 
get_previous()556   link_list* get_previous() { return prev; }
get_animation()557   size_t get_animation() const { return animation_index; }
558   bool get_marker(int* pX, int* pY);
559   bool get_secondary_marker(int* pX, int* pY);
get_frame()560   size_t get_frame() const { return frame_index; }
get_crop_column()561   int get_crop_column() const { return crop_column; }
562 
563   void set_animation(animation_manager* pManager, size_t iAnimation);
564   void set_morph_target(animation* pMorphTarget, int iDurationFactor = 1);
565   void set_frame(size_t iFrame);
566 
set_speed(int iX,int iY)567   void set_speed(int iX, int iY) { speed.dx = iX, speed.dy = iY; }
set_crop_column(int iColumn)568   void set_crop_column(int iColumn) { crop_column = iColumn; }
569 
570   void persist(lua_persist_writer* pWriter) const;
571   void depersist(lua_persist_reader* pReader);
572 
get_animation_manager()573   animation_manager* get_animation_manager() { return manager; }
574 
575  private:
576   animation_manager* manager;
577   animation* morph_target;
578   size_t animation_index;  ///< Animation number.
579   size_t frame_index;      ///< Frame number.
580   union {
581     xy_diff speed;
582     //! Some animations are tied to the marker of another animation and
583     //! hence have a parent rather than a speed.
584     animation* parent;
585   };
586 
587   size_t sound_to_play;
588   int crop_column;
589 };
590 
591 class sprite_render_list : public animation_base {
592  public:
593   sprite_render_list();
594   ~sprite_render_list();
595 
596   void tick();
597   void draw(render_target* pCanvas, int iDestX, int iDestY);
598   bool hit_test(int iDestX, int iDestY, int iTestX, int iTestY);
599 
set_sheet(sprite_sheet * pSheet)600   void set_sheet(sprite_sheet* pSheet) { sheet = pSheet; }
set_speed(int iX,int iY)601   void set_speed(int iX, int iY) { dx_per_tick = iX, dy_per_tick = iY; }
602   void set_lifetime(int iLifetime);
603   void append_sprite(size_t iSprite, int iX, int iY);
is_dead()604   bool is_dead() const { return lifetime == 0; }
605 
606   void persist(lua_persist_writer* pWriter) const;
607   void depersist(lua_persist_reader* pReader);
608 
609  private:
610   struct sprite {
611     size_t index;
612     int x;
613     int y;
614   };
615 
616   sprite_sheet* sheet;
617   sprite* sprites;
618   int sprite_count;
619   int buffer_size;
620 
621   //! Amount to change x per tick
622   int dx_per_tick;
623   //! Amount to change y per tick
624   int dy_per_tick;
625   //! Number of ticks until reports as dead (-1 = never dies)
626   int lifetime;
627 };
628 
629 #endif  // CORSIX_TH_TH_GFX_H_
630