1 #include "Sprites.h"
2 
3 #include "../autogen/sprites.h"
4 #include "../ResourceManager.h"
5 #include "../nx.h"
6 #include "../settings.h"
7 #include "../siflib/sectSprites.h"
8 #include "../siflib/sectStringArray.h"
9 #include "../siflib/sifloader.h"
10 #include "Renderer.h"
11 #include "Surface.h"
12 #include "../Utils/Logger.h"
13 
14 #include <cstring>
15 
16 namespace NXE
17 {
18 namespace Graphics
19 {
20 
21 // offset things like blockl/r/u/d, bounding box etc by the draw point of all
22 // sprites so that these things are consistent with where the sprite appears to be
_offset_by_draw_points()23 void Sprites::_offset_by_draw_points()
24 {
25   for (int s = 0; s < _num_sprites; s++)
26   {
27     int dx = -sprites[s].frame[0].dir[0].drawpoint.x;
28     int dy = -sprites[s].frame[0].dir[0].drawpoint.y;
29 
30     for (int b = 0; b < sprites[s].ndirs; b++)
31       sprites[s].bbox[b].offset(dx, dy);
32     sprites[s].slopebox.offset(dx, dy);
33     sprites[s].solidbox.offset(dx, dy);
34 
35     sprites[s].block_l.offset(dx, dy);
36     sprites[s].block_r.offset(dx, dy);
37     sprites[s].block_u.offset(dx, dy);
38     sprites[s].block_d.offset(dx, dy);
39 
40     for (int f = 0; f < sprites[s].nframes; f++)
41     {
42       for (int d = 0; d < sprites[s].ndirs; d++)
43       {
44         int dx = -sprites[s].frame[f].dir[d].drawpoint.x;
45         int dy = -sprites[s].frame[f].dir[d].drawpoint.y;
46         sprites[s].frame[f].dir[d].pf_bbox.offset(dx, dy);
47       }
48     }
49   }
50 }
51 
52 // for sprites which only have 1 dir (no separate frames for left & right),
53 // create a 2nd identical dir as the rest of the engine doesn't bother
54 // with this complication.
_expand_single_dir_sprites()55 void Sprites::_expand_single_dir_sprites()
56 {
57   for (int s = 0; s < _num_sprites; s++)
58   {
59     if (sprites[s].ndirs == 1)
60     {
61       sprites[s].ndirs = 2;
62       for (int f = 0; f < sprites[s].nframes; f++)
63         sprites[s].frame[f].dir[1] = sprites[s].frame[f].dir[0];
64       sprites[s].bbox[1] = sprites[s].bbox[0];
65     }
66     if (sprites[s].ndirs == 2)
67     {
68       sprites[s].ndirs = 4;
69       for (int f = 0; f < sprites[s].nframes; f++)
70       {
71         sprites[s].frame[f].dir[2] = sprites[s].frame[f].dir[0];
72         sprites[s].frame[f].dir[3] = sprites[s].frame[f].dir[0];
73       }
74       sprites[s].bbox[2] = sprites[s].bbox[0];
75       sprites[s].bbox[3] = sprites[s].bbox[0];
76     }
77   }
78 }
79 
80 // create slope boxes for all sprites, used by the slope-handling routines
81 // these are basically just a form of bounding box describing the bounds of the
82 // blockd points.
_create_slope_boxes()83 void Sprites::_create_slope_boxes()
84 {
85   for (int s = 0; s < _num_sprites; s++)
86   {
87     if (sprites[s].block_d.count != 0)
88     {
89       int leftmost  = 99999;
90       int rightmost = -99999;
91       for (int i = 0; i < sprites[s].block_d.count; i++)
92       {
93         if (sprites[s].block_d[i].x < leftmost)
94           leftmost = sprites[s].block_d[i].x;
95         if (sprites[s].block_d[i].x > rightmost)
96           rightmost = sprites[s].block_d[i].x;
97       }
98 
99       sprites[s].slopebox.x1 = leftmost + 1;
100       sprites[s].slopebox.x2 = rightmost - 1;
101 
102       if (sprites[s].block_u.count)
103         sprites[s].slopebox.y1 = (sprites[s].block_u[0].y + 1);
104       else
105         sprites[s].slopebox.y1 = 0;
106 
107       sprites[s].slopebox.y2 = (sprites[s].block_d[0].y - 1);
108     }
109   }
110 
111   sprites[SPR_MYCHAR].slopebox.y1 += 3;
112 }
113 
_load_sif(const std::string & fname)114 bool Sprites::_load_sif(const std::string &fname)
115 {
116   SIFLoader sif;
117   uint8_t *sheetdata, *spritesdata;
118   int sheetdatalength, spritesdatalength;
119 
120   if (sif.LoadHeader(fname))
121     return false;
122 
123   if (!(sheetdata = sif.FindSection(SIF_SECTION_SHEETS, &sheetdatalength)))
124   {
125     LOG_ERROR("load_sif: file '{}' missing SIF_SECTION_SHEETS", fname.c_str());
126     return false;
127   }
128 
129   if (!(spritesdata = sif.FindSection(SIF_SECTION_SPRITES, &spritesdatalength)))
130   {
131     LOG_ERROR("load_sif: file '{}' missing SIF_SECTION_SPRITES", fname.c_str());
132     return false;
133   }
134 
135   // decode sheets
136   _sheetfiles.clear();
137   if (SIFStringArraySect::Decode(sheetdata, sheetdatalength, &_sheetfiles))
138     return false;
139 
140   // decode sprites
141   if (SIFSpritesSect::Decode(spritesdata, spritesdatalength, &sprites[0], &_num_sprites, MAX_SPRITES))
142   {
143     LOG_ERROR("load_sif: SIFSpritesSect decoder failed");
144     return false;
145   }
146 
147   sif.CloseFile();
148 
149   _create_slope_boxes();
150   _offset_by_draw_points();
151   _expand_single_dir_sprites();
152 
153   return true;
154 }
155 
Sprites()156 Sprites::Sprites() {}
~Sprites()157 Sprites::~Sprites()
158 {
159   close();
160 }
161 
init()162 bool Sprites::init()
163 {
164   memset(_spritesheets, 0, sizeof(_spritesheets));
165 
166   // load sprites info--sheet positions, bounding boxes etc
167   if (!_load_sif(ResourceManager::getInstance()->getPath("sprites.sif")))
168     return false;
169 
170   _num_spritesheets = _sheetfiles.size();
171   return true;
172 }
173 
close()174 void Sprites::close()
175 {
176   flushSheets();
177   _sheetfiles.clear();
178 }
179 
flushSheets()180 void Sprites::flushSheets()
181 {
182   for (int i = 0; i < MAX_SPRITESHEETS; i++)
183   {
184     if (_spritesheets[i] != nullptr)
185     {
186       delete _spritesheets[i];
187       _spritesheets[i] = nullptr;
188     }
189   }
190 }
191 
192 // ensure the given spritesheet is loaded
_loadSheetIfNeeded(int sheetno)193 void Sprites::_loadSheetIfNeeded(int sheetno)
194 {
195   if (!_spritesheets[sheetno])
196   {
197     _spritesheets[sheetno] = new Surface;
198     _spritesheets[sheetno]->loadImage(ResourceManager::getInstance()->getPath(_sheetfiles.at(sheetno)), true);
199   }
200 }
201 
202 // master sprite drawing function
blitSprite(int x,int y,int s,int frame,uint8_t dir,int xoff,int yoff,int wd,int ht,int alpha)203 void Sprites::blitSprite(int x, int y, int s, int frame, uint8_t dir, int xoff, int yoff, int wd, int ht, int alpha)
204 {
205   _loadSheetIfNeeded(sprites[s].spritesheet);
206 
207   dir %= sprites[s].ndirs;
208   SIFDir *sprdir = &sprites[s].frame[frame].dir[dir];
209 
210   _spritesheets[sprites[s].spritesheet]->alpha = alpha;
211 
212   Renderer::getInstance()->drawSurface(_spritesheets[sprites[s].spritesheet], x, y, (sprdir->sheet_offset.x + xoff),
213                 (sprdir->sheet_offset.y + yoff), wd, ht);
214   _spritesheets[sprites[s].spritesheet]->alpha = 255;
215 }
216 
217 // master sprite drawing function
blitSpriteMirrored(int x,int y,int s,int frame,uint8_t dir,int xoff,int yoff,int wd,int ht,int alpha)218 void Sprites::blitSpriteMirrored(int x, int y, int s, int frame, uint8_t dir, int xoff, int yoff, int wd, int ht, int alpha)
219 {
220   _loadSheetIfNeeded(sprites[s].spritesheet);
221 
222   dir %= sprites[s].ndirs;
223   SIFDir *sprdir = &sprites[s].frame[frame].dir[dir];
224 
225   _spritesheets[sprites[s].spritesheet]->alpha = alpha;
226 
227   Renderer::getInstance()->drawSurfaceMirrored(_spritesheets[sprites[s].spritesheet], x, y, (sprdir->sheet_offset.x + xoff),
228                 (sprdir->sheet_offset.y + yoff), wd, ht);
229   _spritesheets[sprites[s].spritesheet]->alpha = 255;
230 }
231 
232 // draw sprite "s" at [x,y]. drawing frame "frame" and dir "dir".
drawSprite(int x,int y,int s,int frame,uint8_t dir)233 void Sprites::drawSprite(int x, int y, int s, int frame, uint8_t dir)
234 {
235   blitSprite(x, y, s, frame, dir, 0, 0, sprites[s].w, sprites[s].h);
236 }
237 
238 // draw sprite "s" at [x,y]. drawing frame "frame" and dir "dir".
drawSpriteMirrored(int x,int y,int s,int frame,uint8_t dir)239 void Sprites::drawSpriteMirrored(int x, int y, int s, int frame, uint8_t dir)
240 {
241   blitSpriteMirrored(x, y, s, frame, dir, 0, 0, sprites[s].w, sprites[s].h);
242 }
243 
244 // draw sprite "s", place it's draw point at [x,y] instead of it's upper-left corner.
drawSpriteAtDp(int x,int y,int s,int frame,uint8_t dir)245 void Sprites::drawSpriteAtDp(int x, int y, int s, int frame, uint8_t dir)
246 {
247   x -= sprites[s].frame[frame].dir[dir].drawpoint.x;
248   y -= sprites[s].frame[frame].dir[dir].drawpoint.y;
249   blitSprite(x, y, s, frame, dir, 0, 0, sprites[s].w, sprites[s].h);
250 }
251 
252 // draw a portion of a sprite, such as a sprite in the middle of "teleporting".
253 // only the area between clipy1 (inclusive) and clipy2 (exclusive) are visible.
drawSpriteClipped(int x,int y,int s,int frame,uint8_t dir,int clipx1,int clipx2,int clipy1,int clipy2)254 void Sprites::drawSpriteClipped(int x, int y, int s, int frame, uint8_t dir, int clipx1, int clipx2, int clipy1,
255                                   int clipy2)
256 {
257   blitSprite(x + clipx1, y + clipy1, s, frame, dir, clipx1, clipy1, (clipx2 - clipx1), (clipy2 - clipy1));
258 }
259 
260 // draw a clipped sprite while clipping only the width.
261 // used for drawing percentage bars, etc.
drawSpriteClipWidth(int x,int y,int s,int frame,int wd)262 void Sprites::drawSpriteClipWidth(int x, int y, int s, int frame, int wd)
263 {
264   blitSprite(x, y, s, frame, 0, 0, 0, wd, sprites[s].h);
265 }
266 
267 // draws a sprite at less than it's actual width by chopping it into two chunks.
268 // on the left, the first "repeat_at" pixels are drawn.
269 // then, the remaining "wd" is drawn from the right half of the sprite.
270 // used for things like drawing the textboxes.
drawSpriteChopped(int x,int y,int s,int frame,int wd,int repeat_at,int alpha)271 void Sprites::drawSpriteChopped(int x, int y, int s, int frame, int wd, int repeat_at, int alpha)
272 {
273   int xoff;
274 
275   if (wd >= sprites[s].w)
276   {
277     blitSprite(x, y, s, frame, 0, 0, 0, sprites[s].w, sprites[s].h, alpha);
278     return;
279   }
280 
281   // draw the left part
282   blitSprite(x, y, s, frame, 0, 0, 0, repeat_at, sprites[s].h, alpha);
283   x += repeat_at;
284   wd -= repeat_at;
285 
286   // draw the rest of it
287   xoff = (sprites[s].w - wd);
288 
289   blitSprite(x, y, s, frame, 0, xoff, 0, wd, sprites[s].h, alpha);
290 }
291 
292 // draws a sprite to any arbitrary width by repeating it over the given distance.
293 // if needed, the rightmost instance of the sprite is clipped.
drawSpriteRepeatingX(int x,int y,int s,int frame,int wd)294 void Sprites::drawSpriteRepeatingX(int x, int y, int s, int frame, int wd)
295 {
296   int wdleft = wd;
297   while (wdleft > 0)
298   {
299     int blitwd = wdleft;
300     if (blitwd > sprites[s].w)
301       blitwd = sprites[s].w;
302 
303     blitSprite(x, y, s, frame, 0, 0, 0, blitwd, sprites[s].h);
304     x += blitwd;
305     wdleft -= blitwd;
306   }
307 }
308 
309 }; // namespace Graphics
310 }; // namespace NXE