1 /*
2  *  This file is part of Dune Legacy.
3  *
4  *  Dune Legacy 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  *  Dune Legacy 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 Dune Legacy.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #ifndef SCREENBORDER_H
19 #define SCREENBORDER_H
20 
21 #include <DataTypes.h>
22 
23 #include <misc/InputStream.h>
24 #include <misc/OutputStream.h>
25 #include <fixmath/FixPoint.h>
26 #include <mmath.h>
27 #include <globals.h>
28 
29 #include <algorithm>
30 
31 #define SCROLLBORDER 3
32 
33 /// This class manages everything that is related to the current view onto the map.
34 class ScreenBorder
35 {
36 public:
37     /**
38         Constructor
39         \param gameBoardRect    this SDL_Rect specifies the rectangle on the screen where the map is shown
40     */
ScreenBorder(const SDL_Rect & gameBoardRect)41     explicit ScreenBorder(const SDL_Rect& gameBoardRect)
42      : mapSizeX(0), mapSizeY(0) {
43 
44         this->gameBoardRect = gameBoardRect;
45 
46         bottomRightCorner.x = gameBoardRect.w;
47         bottomRightCorner.y = gameBoardRect.h;
48         topLeftCornerOnScreen.x = gameBoardRect.x;
49         topLeftCornerOnScreen.y = gameBoardRect.y;
50         bottomRightCornerOnScreen.x = gameBoardRect.x + gameBoardRect.w;
51         bottomRightCornerOnScreen.y = gameBoardRect.y + gameBoardRect.h;
52 
53         numShakingCycles = 0;
54     };
55 
56     /**
57         Destructor
58     */
~ScreenBorder()59     ~ScreenBorder() { }
60 
61     /**
62         Loads the current position on the map from a stream
63         \param stream the stream to load from
64     */
load(InputStream & stream)65     void load(InputStream& stream) {
66         Coord center;
67         center.x = stream.readSint16();
68         center.y = stream.readSint16();
69 
70         setNewScreenCenter(center);
71     }
72 
73     /**
74         Saves the current map position to a stream
75         \param stream the stream to save to
76     */
save(OutputStream & stream)77     void save(OutputStream& stream) const
78     {
79         Coord center = getCurrentCenter();
80         stream.writeSint16(center.x);
81         stream.writeSint16(center.y);
82     }
83 
84     /**
85         Get the current center point of the view in world coordinates.
86         \return current center point in world coordinates.
87     */
getCurrentCenter()88     inline Coord getCurrentCenter() const
89     {
90         return topLeftCorner + shakingOffset + (bottomRightCorner - topLeftCorner)/2;
91     }
92 
93     /**
94         Returns the top left corner of the view in world coordinates.
95         \return current top left corner in world coordinates.
96     */
getTopLeftCorner()97     inline Coord getTopLeftCorner() const
98     {
99         return topLeftCorner + shakingOffset;
100     }
101 
102     /**
103         Returns the bottom right corner of the view in world coordinates.
104         \return current bottom right corner in world coordinates.
105     */
getBottomRightCorner()106     inline Coord getBottomRightCorner() const
107     {
108         return bottomRightCorner + shakingOffset;
109     }
110 
111     /**
112         Returns the position of the left edge of the view in world coordinates.
113         \return current left edge in world coordinates.
114     */
getLeft()115     inline short getLeft() const
116     {
117         return topLeftCorner.x + shakingOffset.x;
118     }
119 
120     /**
121         Returns the position of the top edge of the view in world coordinates.
122         \return current top edge in world coordinates.
123     */
getTop()124     inline short getTop() const
125     {
126         return topLeftCorner.y + shakingOffset.y;
127     }
128 
129     /**
130         Returns the position of the right edge of the view in world coordinates.
131         \return current right edge in world coordinates.
132     */
getRight()133     inline short getRight() const
134     {
135         return bottomRightCorner.x + shakingOffset.x;
136     }
137 
138     /**
139         Returns the position of the bottom edge of the view in world coordinates.
140         \return current bottom edge in world coordinates.
141     */
getBottom()142     inline short getBottom() const
143     {
144         return bottomRightCorner.y + shakingOffset.y;
145     }
146 
147     /**
148         Returns the map coordinate of the top left corner of the current view.
149         \return map coordinate of the top left corner
150     */
getTopLeftTile()151     inline Coord getTopLeftTile() const
152     {
153         return (topLeftCorner + shakingOffset) / TILESIZE;
154     }
155 
156     /**
157         Returns the map coordinate of the bottom right corner of the current view.
158         \return map coordinate of the bottom right corner
159     */
getBottomRightTile()160     inline Coord getBottomRightTile() const
161     {
162         return (bottomRightCorner + shakingOffset) / TILESIZE;
163     }
164 
165     /**
166         There may be a part of the tile at the top left corner that is outside the screen.
167         This method returns how much is outside the screen (in world coordinates).
168         \return the part of the tile that lies outside the screen.
169     */
getCornerOffset()170     inline Coord getCornerOffset() const
171     {
172         return ((topLeftCorner + shakingOffset) / TILESIZE) * TILESIZE - (topLeftCorner + shakingOffset);
173     }
174 
175     /**
176         This method checks if some object is (partly) inside or completely outside the current view.
177         \param objectPosition   object position in world coordinates
178         \param objectSize       the size of the object (in world coordinates)
179         \return true if (partly) inside, false if completly outside
180     */
isInsideScreen(const Coord & objectPosition,const Coord & objectSize)181     inline bool isInsideScreen(const Coord& objectPosition, const Coord& objectSize) const
182     {
183         return (((objectPosition.x + objectSize.x/2) >= topLeftCorner.x + shakingOffset.x)
184                 && ((objectPosition.x - objectSize.x/2) <= bottomRightCorner.x + shakingOffset.x)
185                 && ((objectPosition.y + objectSize.y/2) >= topLeftCorner.y + shakingOffset.y)
186                 && ((objectPosition.y - objectSize.y/2) <= bottomRightCorner.y + shakingOffset.y) );
187     }
188 
189     /**
190         This method checks if a tile is (partly) inside the current view.
191         \param tileLocation the location of the tile in map coordinates
192         \return true if (partly) inside, false if completly outside
193     */
isTileInsideScreen(const Coord & tileLocation)194     inline bool isTileInsideScreen(const Coord& tileLocation) const
195     {
196         return isInsideScreen(tileLocation*TILESIZE + Coord(TILESIZE/2, TILESIZE/2), Coord(TILESIZE,TILESIZE));
197     }
198 
199     /**
200         Sets the current view to a new position.
201         \param newPosition  the center of the new view in world coordinates
202     */
203     void setNewScreenCenter(const Coord& newPosition);
204 
205     /**
206         This method scrolls left
207     */
208     bool scrollLeft();
209 
210     /**
211         This method scrolls right
212     */
213     bool scrollRight();
214 
215         /**
216         This method scrolls up
217     */
218     bool scrollUp();
219 
220     /**
221         This method scrolls down
222     */
223     bool scrollDown();
224 
225     /**
226         This method converts from world to screen coordinates.
227         \param x    the x position in world coordinates
228         \return the x-coordinate on the screen
229     */
world2screenX(int x)230     inline int world2screenX(int x) const
231     {
232         return world2zoomedWorld(x - topLeftCorner.x + shakingOffset.x + topLeftCornerOnScreen.x);
233     }
234 
235     /**
236         This method converts from world to screen coordinates.
237         \param x    the x position in world coordinates
238         \return the x-coordinate on the screen
239     */
world2screenX(float x)240     inline int world2screenX(float x) const
241     {
242         return world2zoomedWorld(x - (float) topLeftCorner.x + (float) shakingOffset.x + (float) topLeftCornerOnScreen.x);
243     }
244 
245     /**
246         This method converts from world to screen coordinates.
247         \param x    the x position in world coordinates
248         \return the x-coordinate on the screen
249     */
world2screenX(FixPoint x)250     inline int world2screenX(FixPoint x) const
251     {
252         return world2zoomedWorld(x.toFloat() - (float) topLeftCorner.x + (float) shakingOffset.x + (float) topLeftCornerOnScreen.x);
253     }
254 
255     /**
256         This method converts from world to screen coordinates.
257         \param y    the y position in world coordinates
258         \return the y-coordinate on the screen
259     */
world2screenY(int y)260     inline int world2screenY(int y) const
261     {
262         return world2zoomedWorld(y - topLeftCorner.y + shakingOffset.y + topLeftCornerOnScreen.y);
263     }
264 
265     /**
266         This method converts from world to screen coordinates.
267         \param y    the y position in world coordinates
268         \return the y-coordinate on the screen
269     */
world2screenY(float y)270     inline int world2screenY(float y) const
271     {
272         return world2zoomedWorld(y - (float) topLeftCorner.y + (float) shakingOffset.y + (float) topLeftCornerOnScreen.y);
273     }
274 
275     /**
276         This method converts from world to screen coordinates.
277         \param y    the y position in world coordinates
278         \return the y-coordinate on the screen
279     */
world2screenY(FixPoint y)280     inline int world2screenY(FixPoint y) const
281     {
282         return world2zoomedWorld(y.toFloat() - (float) topLeftCorner.y + (float) shakingOffset.y + (float) topLeftCornerOnScreen.y);
283     }
284 
285     /**
286         This method converts from screen to world coordinates.
287         \param x    the x coordinate on the screen
288         \return the x-position in world coordinates
289     */
screen2worldX(int x)290     inline int screen2worldX(int x) const
291     {
292         return zoomedWorld2world(x) - topLeftCornerOnScreen.x + topLeftCorner.x + shakingOffset.x;
293     }
294 
295     /**
296         This method converts from screen to world coordinates.
297         \param y    the y coordinate on the screen
298         \return the y-position in world coordinates
299     */
screen2worldY(int y)300     inline int screen2worldY(int y) const
301     {
302         return zoomedWorld2world(y) - topLeftCornerOnScreen.y + topLeftCorner.y + shakingOffset.y;
303     }
304 
305     /**
306         This method converts from screen to map coordinates.
307         \param x    the x coordinate on the screen
308         \return the x-coordinate of the tile at position x in map coordinates
309     */
screen2MapX(int x)310     inline int screen2MapX(int x) const
311     {
312         return screen2worldX(x)/TILESIZE;
313     };
314 
315     /**
316         This method converts from screen to map coordinates.
317         \param y    the y coordinate on the screen
318         \return the y-coordinate of the tile at position y in map coordinates
319     */
screen2MapY(int y)320     inline int screen2MapY(int y) const
321     {
322         return screen2worldY(y)/TILESIZE;
323     };
324 
325     /**
326         This method checks if the specified x,y coordinate is within the map
327         \param  x the x coordinate in screen coordinates
328         \param  y the y coordinate in screen coordinates
329         \return true, if inside, false otherwise
330     */
isScreenCoordInsideMap(int x,int y)331     inline bool isScreenCoordInsideMap(int x, int y) const {
332         return (zoomedWorld2world(x) >= topLeftCornerOnScreen.x  && zoomedWorld2world(x) < bottomRightCornerOnScreen.x
333                 && zoomedWorld2world(y) >= topLeftCornerOnScreen.y  && zoomedWorld2world(y) < bottomRightCornerOnScreen.y);
334     }
335 
336     /**
337         This method adjusts the screen border to the current map size.
338         \param newMapSizeX         the number of map tiles in x direction
339         \param newMapSizeY         the number of map tiles in y direction
340     */
341     void adjustScreenBorderToMapsize(int newMapSizeX, int newMapSizeY);
342 
343 
shakeScreen(int numShakingCycles)344     void shakeScreen(int numShakingCycles) {
345         this->numShakingCycles += numShakingCycles;
346         if(this->numShakingCycles > 2*numShakingCycles) {
347             this->numShakingCycles = 2*numShakingCycles;
348         }
349     }
350 
update()351     void update() {
352         if(numShakingCycles > 0) {
353             int offsetMax = std::min(TILESIZE-1,numShakingCycles);
354 
355             shakingOffset.x = (rand() % offsetMax) - offsetMax/2;
356             shakingOffset.y = (rand() % offsetMax) - offsetMax/2;
357 
358             numShakingCycles--;
359         }
360     }
361 
362 private:
363     SDL_Rect gameBoardRect;         ///< the complete game board rectangle
364 
365     int mapSizeX;                   ///< The number of tiles in x direction
366     int mapSizeY;                   ///< The number of tiles in y direction
367 
368     Coord topLeftCorner;            ///< the position of the top left corner in world coordinates
369     Coord bottomRightCorner;        ///< the position of the bottom right corner in world coordinates
370 
371     Coord shakingOffset;            ///< this offset is added while shaking the screen (e.g. when a death hand explodes)
372 
373     Coord topLeftCornerOnScreen;    ///< the position of the top left corner in screen coordinates
374     Coord bottomRightCornerOnScreen;///< the position of the bottom right corner in screen coordinates
375 
376     int numShakingCycles;           ///< the number of cycles the screen will shake
377 };
378 
379 #endif //SCREENBORDER
380