1 // Crimson Fields -- a game of tactical warfare
2 // Copyright (C) 2000-2007 Jens Granseuer
3 //
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 2 of the License, or
7 // (at your option) any later version.
8 //
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.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 //
18 
19 ////////////////////////////////////////////////////////////////////////
20 // mapview.cpp
21 ////////////////////////////////////////////////////////////////////////
22 
23 #include "mapview.h"
24 
25 ////////////////////////////////////////////////////////////////////////
26 // NAME       : MapView::MapView
27 // DESCRIPTION: Partly set up a map viewport. To be usable, SetMap() and
28 //              Enable() must be called afterwards.
29 // PARAMETERS : display - surface on which to display the map
30 //              bounds  - viewport position and size on surface
31 //              flags   - viewport flags (see mapview.h for details)
32 // RETURNS    : -
33 ////////////////////////////////////////////////////////////////////////
34 
MapView(Surface * display,const Rect & bounds,unsigned short flags)35 MapView::MapView( Surface *display, const Rect &bounds, unsigned short flags ) :
36          Rect( bounds ) {
37   surface = display;
38 
39   map = NULL;
40   shader_map = NULL;
41 
42   SetFlags( flags );
43 }
44 
45 ////////////////////////////////////////////////////////////////////////
46 // NAME       : MapView::InitOffsets
47 // DESCRIPTION: Set offsets for map display and scrolling. Call after
48 //              having set a new map for the viewport.
49 // PARAMETERS : -
50 // RETURNS    : -
51 ////////////////////////////////////////////////////////////////////////
52 
InitOffsets(void)53 void MapView::InitOffsets( void ) {
54   maxx = MAX( 0, map->Width() * (TileWidth() - TileShiftX()) + TileShiftX() - w );
55   maxy = MAX( 0, map->Height() * TileHeight() + TileShiftY() - h );
56 }
57 
58 ////////////////////////////////////////////////////////////////////////
59 // NAME       : MapView::Resize
60 // DESCRIPTION: Set a new viewport size and position.
61 // PARAMETERS : bounds - new viewport size and position
62 // RETURNS    : -
63 ////////////////////////////////////////////////////////////////////////
64 
Resize(const Rect & bounds)65 void MapView::Resize( const Rect &bounds ) {
66   x = bounds.x;
67   y = bounds.y;
68   w = bounds.w;
69   h = bounds.h;
70   InitOffsets();
71 
72   if ( curx > maxx ) curx = maxx;
73   if ( cury > maxy ) cury = maxy;
74 
75   if ( HexVisible( cursor ) ) Draw();
76   else CenterOnHex( cursor );
77 }
78 
79 ////////////////////////////////////////////////////////////////////////
80 // NAME       : MapView::SetMap
81 // DESCRIPTION: Assign another map to this viewport.
82 // PARAMETERS : map - map to display; the map must have already had
83 //                    a level set assigned
84 // RETURNS    : -
85 ////////////////////////////////////////////////////////////////////////
86 
SetMap(Map * map)87 void MapView::SetMap( Map *map ) {
88   if ( shader_map ) delete [] shader_map;
89 
90   cursor = Point( -1, -1 );
91   cursor_image = IMG_CURSOR_IDLE;
92   this->map = map;
93 
94   InitOffsets();
95   shader_map = new signed char [map->Width() * map->Height()];
96 }
97 
98 ////////////////////////////////////////////////////////////////////////
99 // NAME       : MapView::Enable
100 // DESCRIPTION: Set map offsets back to 0 and enable map display.
101 // PARAMETERS : -
102 // RETURNS    : -
103 ////////////////////////////////////////////////////////////////////////
104 
Enable(void)105 void MapView::Enable( void ) {
106   curx = cury = 0;
107   UnsetFlags( MV_DISABLE );
108 }
109 
110 ////////////////////////////////////////////////////////////////////////
111 // NAME       : MapView::Disable
112 // DESCRIPTION: Hide the actual map and display just blackness.
113 // PARAMETERS : -
114 // RETURNS    : -
115 ////////////////////////////////////////////////////////////////////////
116 
Disable(void)117 void MapView::Disable( void ) {
118   SetFlags( MV_DISABLE );
119 }
120 
121 ////////////////////////////////////////////////////////////////////////
122 // NAME       : MapView::Draw
123 // DESCRIPTION: Draw a part of the map onto the window surface.
124 // PARAMETERS : x - of the viewport (!) area to update
125 //              y - of the viewport area to update
126 //              w - width of the update area
127 //              h - height of the update area
128 // RETURNS    : -
129 ////////////////////////////////////////////////////////////////////////
130 
Draw(short x,short y,unsigned short w,unsigned short h) const131 void MapView::Draw( short x, short y, unsigned short w, unsigned short h ) const {
132   surface->FillRect( *this, Color(CF_COLOR_SHADOW) );
133   if ( Enabled() ) {
134     DrawMap( curx + x, cury + y, w, h, surface, x + this->x, y + this->y );
135   }
136 }
137 
138 ////////////////////////////////////////////////////////////////////////
139 // NAME       : MapView::DrawMap
140 // DESCRIPTION: Draw a part of the map onto a surface.
141 // PARAMETERS : x    - leftmost pixel of the map (!) to paint
142 //              y    - topmost pixel to paint
143 //              w    - width of the map part to draw
144 //              h    - height of the map part
145 //              dest - destination surface
146 //              dx   - where to start drawing on the surface
147 //              dy   - where to start drawing vertically
148 // RETURNS    : -
149 ////////////////////////////////////////////////////////////////////////
150 
DrawMap(short x,short y,unsigned short w,unsigned short h,Surface * dest,short dx,short dy) const151 void MapView::DrawMap( short x, short y, unsigned short w,
152               unsigned short h, Surface *dest, short dx, short dy ) const {
153   int hx1 = MinXHex( x );
154   int hy1 = MinYHex( y );
155   int hx2 = MaxXHex( x, w );
156   int hy2 = MaxYHex( y, h );
157 
158   Rect clip( dx, dy, w, h );
159   int sx, sy, tx, ty, yoff;
160   Point hex;
161 
162   for ( tx = hx1; tx <= hx2; ++tx ) {
163     sx = tx * (TileWidth() - TileShiftX()) - x + this->x;
164 
165     if ( tx & 1 ) yoff = TileShiftY() - y;
166     else yoff = -y;
167     yoff += this->y;
168 
169     for ( ty = hy1; ty <= hy2; ++ty ) {
170       sy = ty * TileHeight() + yoff;
171       hex = Point( tx, ty );
172 
173       // draw the terrain image
174       DrawTerrain( map->HexImage( hex ), dest, sx, sy, clip );
175 
176       // draw unit
177       if ( Unit *u = map->GetUnit( hex ) ) {
178         DrawUnit( u->Image(), dest, sx, sy, clip );
179         if ( !u->IsReady() ) DrawTerrain( IMG_NOT_AVAILABLE, dest, sx, sy, clip );
180 
181         // draw unit health
182         if ( UnitStatsEnabled() )
183           DrawUnitHealth( u->GroupSize(), dest, sx, sy, clip );
184       }
185 
186       // draw fog
187       if ( FogEnabled() && (shader_map[map->Hex2Index(hex)] == -1) )
188         DrawFog( dest, sx, sy, clip );
189 
190       // draw cursor
191       if ( CursorEnabled() && (hex == cursor) )
192         DrawTerrain( cursor_image, dest, sx, sy, clip );
193     }
194   }
195 }
196 
197 ////////////////////////////////////////////////////////////////////////
198 // NAME       : MapView::Pixel2Hex
199 // DESCRIPTION: Convert viewport coordinates to hex coordinates.
200 // PARAMETERS : px  - pixel x relative to viewport border
201 //              py  - pixel y relative to viewport border
202 //              hex - buffer to hold the resulting hex
203 // RETURNS    : 0 on success, -1 on error (invalid pixels);
204 //              hex will contain -1, -1 then
205 ////////////////////////////////////////////////////////////////////////
206 
Pixel2Hex(short px,short py,Point & hex) const207 int MapView::Pixel2Hex( short px, short py, Point &hex ) const {
208 
209   if ( Contains( px, py ) ) {
210     short hx = px + curx - x, hy = py + cury - y;
211 
212     hex.x = hx / (TileWidth() - TileShiftX());
213     hex.y = (hy + (hex.x & 1) * TileShiftY()) / TileHeight() - (hex.x & 1);
214 
215     // calculate pixel position relative to selected hex
216     hx %= TileWidth() - TileShiftX();
217     hy -= hex.y * TileHeight() + (hex.x & 1) * TileShiftY();
218 
219     const Surface &mask = map->GetTerrainSet()->HexMask();
220     if ( mask.GetPixel( hx, hy ) == mask.GetColorKey() ) {
221       if ( hx < (TileWidth() / 2) ) --hex.x;
222       else ++hex.x;
223 
224       if ( hy < (TileHeight() / 2) ) {
225         if ( hex.x & 1 ) --hex.y; // odd columns
226       } else {
227         if ( !(hex.x & 1) ) ++hex.y; // even columns
228       }
229     }
230 
231     if ( map->Contains( hex ) ) return 0;
232   }
233 
234   hex = Point( -1, -1 );
235   return -1;
236 }
237 
238 ////////////////////////////////////////////////////////////////////////
239 // NAME       : MapView::Hex2Pixel
240 // DESCRIPTION: Get the pixel coordinates (top left edge) of a hex
241 //              relative to the display surface offsets.
242 // PARAMETERS : hex - hex coordinates
243 // RETURNS    : Point containing the position of the hex in pixels
244 ////////////////////////////////////////////////////////////////////////
245 
Hex2Pixel(const Point & hex) const246 Point MapView::Hex2Pixel( const Point &hex ) const {
247   return Point( hex.x * (TileWidth() - TileShiftX()) - curx + x,
248                 hex.y * TileHeight() + (hex.x & 1) * TileShiftY() - cury + y );
249 }
250 
251 ////////////////////////////////////////////////////////////////////////
252 // NAME       : MapView::HexVisible
253 // DESCRIPTION: Check whether a given hex is currently visible in the
254 //              map viewport area.
255 // PARAMETERS : hex - hex position
256 // RETURNS    : true if hex (or part of it) on screen, false otherwise
257 ////////////////////////////////////////////////////////////////////////
258 
HexVisible(const Point & hex) const259 bool MapView::HexVisible( const Point &hex ) const {
260   Point p = Hex2Pixel( hex );  // get absolute pixel values
261 
262   return( (p.x >= x) && (p.x <= x + w - TileWidth()) &&
263           (p.y >= y) && (p.y <= y + h - TileHeight()) );
264 }
265 
266 ////////////////////////////////////////////////////////////////////////
267 // NAME       : MapView::UpdateHex
268 // DESCRIPTION: Redraw a single hex.
269 // PARAMETERS : hex - hex to update
270 // RETURNS    : Rect describing the surface area that has been updated
271 //              and needs to be refreshed
272 ////////////////////////////////////////////////////////////////////////
273 
UpdateHex(const Point & hex)274 Rect MapView::UpdateHex( const Point &hex ) {
275   if ( !Enabled() ) return Rect(0,0,0,0);
276 
277   Point p = Hex2Pixel( hex );  // get absolute position
278 
279   Rect clip( p.x, p.y, TileWidth(), TileHeight() );   // create clip rect
280   clip.Clip( *this );
281 
282   // draw the terrain image
283   DrawTerrain( map->HexImage( hex ), surface, p.x, p.y, clip );
284 
285   // draw unit
286   if ( Unit *u = map->GetUnit( hex ) ) {
287     DrawUnit( u->Image(), surface, p.x, p.y, clip );
288     if ( !u->IsReady() ) DrawTerrain( IMG_NOT_AVAILABLE, surface, p.x, p.y, clip );
289 
290     // draw unit health
291     if ( UnitStatsEnabled() )
292       DrawUnitHealth( u->GroupSize(), surface, p.x, p.y, clip );
293   }
294 
295   // draw fog
296   if ( FogEnabled() && (shader_map[map->Hex2Index(hex)] == -1) )
297     DrawFog( surface, p.x, p.y, clip );
298 
299   // draw cursor
300   if ( CursorEnabled() && (hex == cursor) )
301     DrawTerrain( cursor_image, surface, p.x, p.y, clip );
302 
303   return clip;
304 }
305 
306 ////////////////////////////////////////////////////////////////////////
307 // NAME       : MapView::DrawUnitHealth
308 // DESCRIPTION: Draw a unit health bar.
309 // PARAMETERS : health - unit health indicator
310 //              dest   - destination surface
311 //              px     - horizontal offset on surface
312 //              py     - vertical offset on surface
313 //              clip   - clipping rectangle
314 // RETURNS    : -
315 ////////////////////////////////////////////////////////////////////////
316 
DrawUnitHealth(unsigned char health,Surface * dest,short px,short py,const Rect & clip) const317 void MapView::DrawUnitHealth( unsigned char health, Surface *dest,
318               short px, short py, const Rect &clip ) const {
319   Color hcol;
320   if ( health >= 5 ) hcol = Color(0x62,0xAA,0x46);      // green
321   else if ( health >= 3 ) hcol = Color(0xFE,0xAA,0x04); // yellow
322   else hcol = Color(0xFE,0x26,0x26);                    // red
323 
324   unsigned short hp = (TileWidth() - 2 * TileShiftX() - 4) /
325                       (MAX_GROUP_SIZE + 2);
326   unsigned short barw = hp * MAX_GROUP_SIZE + 2;
327 
328   Rect outer( px + (TileWidth() - barw) / 2,
329               py + TileHeight() - hp - 4, barw, hp + 3 );
330   Rect inner( outer.x + 1, outer.y + 1, health * hp, hp + 1 );
331 
332   outer.Clip( clip );
333   inner.Clip( clip );
334 
335   dest->FillRect( outer, Color(CF_COLOR_BLACK) );
336   dest->FillRect( inner, hcol );
337 }
338 
339 ////////////////////////////////////////////////////////////////////////
340 // NAME       : MapView::CenterOnHex
341 // DESCRIPTION: Center the display on a given hex if possible.
342 // PARAMETERS : hex - hex position
343 // RETURNS    : -
344 ////////////////////////////////////////////////////////////////////////
345 
CenterOnHex(const Point & hex)346 void MapView::CenterOnHex( const Point &hex ) {
347   Point p = Hex2Pixel( hex );
348 
349   short off = p.x + (TileWidth() - w) / 2 + curx;
350   if ( off > maxx ) curx = maxx;
351   else if ( off < 0 ) curx = 0;
352   else curx = off;
353 
354   off = p.y + (TileHeight() - h) / 2 + cury;
355   if ( off > maxy ) cury = maxy;
356   else if ( off < 0 ) cury = 0;
357   else cury = off;
358   Draw();
359 }
360 
361 ////////////////////////////////////////////////////////////////////////
362 // NAME       : MapView::MinXHex
363 // DESCRIPTION: Get the leftmost visible hex number.
364 // PARAMETERS : x - leftmost pixel. If a value of -1 is given, use the
365 //                  current viewport settings (curx) to determine the
366 //                  hex.
367 // RETURNS    : leftmost visible hex column
368 ////////////////////////////////////////////////////////////////////////
369 
MinXHex(short x) const370 unsigned short MapView::MinXHex( short x ) const {
371   if ( x == -1 ) x = curx;
372   return MAX( (x - TileShiftX()) / (TileWidth() - TileShiftX()), 0 );
373 }
374 
375 ////////////////////////////////////////////////////////////////////////
376 // NAME       : MapView::MinYHex
377 // DESCRIPTION: Get the topmost visible hex number.
378 // PARAMETERS : y - topmost pixel. If a value of -1 is given, use the
379 //                  current viewport settings (cury) to determine the
380 //                  hex.
381 // RETURNS    : topmost visible hex row
382 ////////////////////////////////////////////////////////////////////////
383 
MinYHex(short y) const384 unsigned short MapView::MinYHex( short y ) const {
385   if ( y == -1 ) y = cury;
386   return MAX( (y - TileShiftY()) / TileHeight(), 0 );
387 }
388 
389 ////////////////////////////////////////////////////////////////////////
390 // NAME       : MapView::MaxXHex
391 // DESCRIPTION: Get the rightmost visible hex number.
392 // PARAMETERS : x - leftmost pixel. If a value of -1 is given, use the
393 //                  current viewport settings (curx/MapView::Width())
394 //                  to determine the hex.
395 //              w - display width
396 // RETURNS    : rightmost visible hex column
397 ////////////////////////////////////////////////////////////////////////
398 
MaxXHex(short x,unsigned short w) const399 unsigned short MapView::MaxXHex( short x, unsigned short w ) const {
400   if ( x == -1 ) {
401     x = curx;
402     w = Width();
403   }
404   return MIN( (x + w) / (TileWidth() - TileShiftX()), map->Width() - 1 );
405 }
406 
407 ////////////////////////////////////////////////////////////////////////
408 // NAME       : MapView::MaxYHex
409 // DESCRIPTION: Get the lowest visible hex number.
410 // PARAMETERS : y - topmost pixel. If a value of -1 is given, use the
411 //                  current viewport settings (cury/MapView::Width())
412 //                  to determine the hex.
413 //              h - display height
414 // RETURNS    : lowest visible hex row
415 ////////////////////////////////////////////////////////////////////////
416 
MaxYHex(short y,unsigned short h) const417 unsigned short MapView::MaxYHex( short y, unsigned short h ) const {
418   if ( y == -1 ) {
419     y = cury;
420     h = Height();
421   }
422   return MIN( (y + h) / TileHeight(), map->Height() - 1 );
423 }
424 
425 ////////////////////////////////////////////////////////////////////////
426 // NAME       : MapView::CheckScroll
427 // DESCRIPTION: Look at the current cursor position and map offsets and
428 //              decide whether we need to scroll the display. If so, do
429 //              it.
430 // PARAMETERS : -
431 // RETURNS    : TRUE if display was scrolled, FALSE otherwise
432 ////////////////////////////////////////////////////////////////////////
433 
CheckScroll(void)434 bool MapView::CheckScroll( void ) {
435   if ( cursor.x == -1 ) return false;
436 
437   Point p = Hex2Pixel( cursor );
438 
439   if ( (curx > 0) && (p.x <= x + TileWidth()) ) p.x = -w / 2;
440   else if ( (curx < maxx) && (w + x - p.x - TileWidth() <= TileWidth()) )
441     p.x = w / 2;
442   else p.x = 0;
443 
444   if ( (cury > 0) && (p.y <= y + TileHeight()) ) p.y = -h / 2;
445   else if ( (cury < maxy) && (h + y - p.y - TileHeight() <= TileHeight()) )
446     p.y = h / 2;
447   else p.y = 0;
448 
449   if ( p.x || p.y ) {
450     Scroll( p.x, p.y );
451     return true;
452   }
453   return false;
454 }
455 
456 ////////////////////////////////////////////////////////////////////////
457 // NAME       : MapView::Scroll
458 // DESCRIPTION: Scroll the currently visible map area.
459 // PARAMETERS : px - pixels to scroll horizontally
460 //              py - pixels to scroll vertically
461 // RETURNS    : -
462 ////////////////////////////////////////////////////////////////////////
463 
Scroll(short px,short py)464 void MapView::Scroll( short px, short py ) {
465   if ( curx + px < 0 ) px = -curx;
466   else if ( curx + px > maxx ) px = maxx - curx;
467   curx += px;
468 
469   if ( cury + py < 0 ) py = -cury;
470   else if ( cury + py > maxy ) py = maxy - cury;
471   cury += py;
472 
473 #ifdef CF_SDL_LOCAL_BLIT
474   // right now SDL cannot blit parts of a surface to another
475   // place on the same surface if both areas overlap
476 
477   // calculate dirty rectangles
478   Rect d1( 0, 0, 0, 0 );  // dirty 1
479   Rect d2( 0, 0, 0, 0 );  // dirty 2
480   Rect c( 0, 0, w, h );   // copy, still valid
481 
482   if ( px > 0 ) {
483     d1 = Rect( w - px, 0, px, h );
484     c.x = px;
485     c.w = w - px;
486   } else if ( px < 0 ) {
487     d1 = Rect( 0, 0, -px, h );
488     c.w = w + px;
489   }
490 
491   if ( py > 0 ) {
492     d2 = Rect( 0, h - py, w, py );
493     c.y = py;
494     c.h = h - py;
495   } else if ( py < 0 ) {
496     d2 = Rect( 0, 0, w, -py );
497     c.h = h + py;
498   }
499 
500   // eliminate overlapping parts
501   if ( (d1.w > 0) && (d2.w > 0) ) {
502     if ( d1.x == d2.x ) d2.x += d1.w;
503     d2.w = MAX( 0, d2.w - d1.w );
504   }
505 
506   // copy valid part to current position
507   if ( c.w && c.h )
508     Blit( this, c, (c.x == 0) ? -px : 0, (c.y == 0) ? -py : 0 );
509 
510   // update dirty parts
511   if ( d1.w && d1.h ) Draw( d1.x, d1.y, d1.w, d1.h );
512   if ( d2.w && d2.h ) Draw( d2.x, d2.y, d2.w, d2.h );
513 #else
514   Draw();
515 #endif
516 }
517 
518 ////////////////////////////////////////////////////////////////////////
519 // NAME       : MapView::SetCursor
520 // DESCRIPTION: Move the cursor onto another hex or remove it from the
521 //              viewport.
522 // PARAMETERS : hex - hex to set the cursor to. A hex of (-1, -1) will
523 //                    remove the cursor entirely. If the cursor was
524 //                    disabled and hex denotes a valid hex, this will
525 //                    reenable the cursor.
526 // RETURNS    : Rect describing the surface area that was updated
527 ////////////////////////////////////////////////////////////////////////
528 
SetCursor(const Point & hex)529 Rect MapView::SetCursor( const Point &hex ) {
530   Rect update( 0, 0, 0, 0 );
531 
532   if ( (hex.x == -1) && (hex.y == -1) ) {  // remove cursor
533     if ( CursorEnabled() ) {
534       Point old = cursor;
535       cursor = Point( -1, -1 );
536 
537       // update old cursor position
538       update = UpdateHex( old );
539       SetFlags(MV_DISABLE_CURSOR);
540     }
541   } else {
542     UnsetFlags(MV_DISABLE_CURSOR);
543     cursor = hex;
544     update = UpdateHex( hex );
545     if ( FlagSet(MV_AUTOSCROLL) && CheckScroll() ) update = *this;
546   }
547   return update;
548 }
549 
550