Avoid crashing when trying to access info file.
[supertux.git] / src / camera.cpp
1 //  $Id$
2 //
3 //  SuperTux -  A Jump'n Run
4 //  Copyright (C) 2004 Matthias Braun <matze@braunis.de
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <stdexcept>
21 #include <sstream>
22 #include <cmath>
23
24 #include "camera.h"
25 #include "utils/lispreader.h"
26 #include "utils/lispwriter.h"
27 #include "player.h"
28 #include "tilemap.h"
29 #include "gameloop.h"
30 #include "app/globals.h"
31 #include "sector.h"
32
33 using namespace SuperTux;
34
35 Camera::Camera(Sector* newsector)
36   : sector(newsector), do_backscrolling(true), scrollchange(NONE),
37     auto_idx(0), auto_t(0)
38 {
39   mode = NORMAL;
40 }
41
42 Camera::~Camera()
43 {
44 }
45
46 const Vector&
47 Camera::get_translation() const
48 {
49   return translation;
50 }
51
52 void
53 Camera::read(LispReader& reader)
54 {
55   std::string modename;
56   
57   reader.read_string("mode", modename);
58   if(modename == "normal") {
59     mode = NORMAL;
60
61     do_backscrolling = true;
62     reader.read_bool("backscrolling", do_backscrolling);
63   } else if(modename == "autoscroll") {
64     mode = AUTOSCROLL;
65     
66     lisp_object_t* cur = 0;
67     reader.read_lisp("path", cur);
68     if(cur == 0) {
69       throw std::runtime_error("No path specified in autoscroll camera.");
70     }
71     float speed = .5;
72     while(!lisp_nil_p(cur)) {
73       if(strcmp(lisp_symbol(lisp_car(lisp_car(cur))), "point") != 0) {
74         std::cerr << "Warning: unknown token in camera path.\n";
75         continue;
76       }
77            
78       LispReader reader(lisp_cdr(lisp_car(cur)));
79
80       ScrollPoint point;
81       if(!reader.read_float("x", point.position.x) ||
82          !reader.read_float("y", point.position.y)) {
83         throw std::runtime_error("x and y missing in point of camerapath");
84       }
85       reader.read_float("speed", speed);
86       point.speed = speed;
87       scrollpoints.push_back(point);
88
89       cur = lisp_cdr(cur);
90     }
91   } else if(modename == "manual") {
92     mode = MANUAL;
93   } else {
94     std::stringstream str;
95     str << "invalid camera mode '" << modename << "'found in worldfile.";
96     throw std::runtime_error(str.str());
97   }
98 }
99
100 void
101 Camera::write(LispWriter& writer)
102 {
103   writer.start_list("camera");
104   
105   if(mode == NORMAL) {
106     writer.write_string("mode", "normal");
107     writer.write_bool("backscrolling", do_backscrolling);
108   } else if(mode == AUTOSCROLL) {
109     writer.write_string("mode", "autoscroll");
110     writer.start_list("path");
111     for(std::vector<ScrollPoint>::iterator i = scrollpoints.begin();
112         i != scrollpoints.end(); ++i) {
113       writer.start_list("point");
114       writer.write_float("x", i->position.x);
115       writer.write_float("y", i->position.y);
116       writer.write_float("speed", i->speed);
117       writer.end_list("point");
118     }
119
120     writer.end_list("path");
121   } else if(mode == MANUAL) {
122     writer.write_string("mode", "manual");
123   }
124                      
125   writer.end_list("camera");
126 }
127
128 void
129 Camera::reset(const Vector& tuxpos)
130 {
131   translation.x = tuxpos.x - screen->w/3 * 2;
132   translation.y = tuxpos.y - screen->h/2;
133   keep_in_bounds();
134 }
135
136 static const float EPSILON = .00001;
137 static const float max_speed_y = 1.4;
138
139 void
140 Camera::action(float elapsed_time)
141 {
142   if(mode == NORMAL)
143     scroll_normal(elapsed_time);
144   else if(mode == AUTOSCROLL)
145     scroll_autoscroll(elapsed_time);
146 }
147
148 void
149 Camera::keep_in_bounds()
150 {
151   float width = sector->solids->get_width() * 32;
152   float height = sector->solids->get_height() * 32;
153
154   // don't scroll before the start or after the level's end
155   if(translation.y > height - screen->h)
156     translation.y = height - screen->h;
157   if(translation.y < 0)                                      
158     translation.y = 0; 
159   if(translation.x > width - screen->w)
160     translation.x = width - screen->w;
161   if(translation.x < 0)
162     translation.x = 0;                                         
163 }
164
165 void
166 Camera::scroll_normal(float elapsed_time)
167 {
168   assert(sector != 0);
169   Player* player = sector->player;
170   
171   // check that we don't have division by zero later
172   if(elapsed_time < EPSILON)
173     return;
174
175   /****** Vertical Scrolling part ******/
176   bool do_y_scrolling = true;
177
178   if(player->dying || sector->solids->get_height() == 19)
179     do_y_scrolling = false;
180
181   if(do_y_scrolling) {
182     // target_y is the high we target our scrolling at. This is not always the
183     // high of the player, but if he is jumping upwards we should use the
184     // position where he last touched the ground.
185     float target_y; 
186     if(player->fall_mode == Player::JUMPING)
187       target_y = player->last_ground_y + player->base.height;
188     else
189       target_y = player->base.y + player->base.height;
190
191     // delta_y is the distance we'd have to travel to directly reach target_y
192     float delta_y = translation.y - (target_y - screen->h/2);
193     // speed is the speed the camera would need to reach target_y in this frame
194     float speed_y = delta_y / elapsed_time;
195
196     // limit the camera speed when jumping upwards
197     if(player->fall_mode != Player::FALLING 
198         && player->fall_mode != Player::TRAMPOLINE_JUMP) {
199       if(speed_y > max_speed_y)
200         speed_y = max_speed_y;
201       else if(speed_y < -max_speed_y)
202         speed_y = -max_speed_y;
203     }
204
205     // finally scroll with calculated speed
206     translation.y -= speed_y * elapsed_time;
207   }
208
209   /****** Horizontal scrolling part *******/
210
211   // our camera is either in leftscrolling, rightscrolling or nonscrollingmode.
212   
213   // when suddenly changing directions while scrolling into the other direction.
214   // abort scrolling, since tux might be going left/right at a relatively small
215   // part of the map (like when jumping upwards)
216   if((player->dir == ::LEFT && scrollchange == RIGHT)
217       || (player->dir == ::RIGHT && scrollchange == LEFT))
218     scrollchange = NONE;
219   // when in left 1/3rd of screen scroll left
220   if(player->base.x < translation.x + screen->w/3 - 16 && do_backscrolling)
221     scrollchange = LEFT;
222   // scroll right when in right 1/3rd of screen
223   else if(player->base.x > translation.x + screen->w/3*2 + 16)
224     scrollchange = RIGHT;
225
226   // calculate our scroll target depending on scroll mode
227   float target_x;
228   if(scrollchange == LEFT)
229     target_x = player->base.x - screen->w/3*2;
230   else if(scrollchange == RIGHT)
231     target_x = player->base.x - screen->w/3;
232   else
233     target_x = translation.x;
234
235   // that's the distance we would have to travel to reach target_x
236   float delta_x = translation.x - target_x;
237   // the speed we'd need to travel to reach target_x in this frame
238   float speed_x = 1.3 * delta_x / elapsed_time;
239
240   // limit our speed
241   float maxv = 1.3 * (1 + fabsf(player->physic.get_velocity_x() * 1.3));
242   if(speed_x > maxv)
243     speed_x = maxv;
244   else if(speed_x < -maxv)
245     speed_x = -maxv;
246  
247   // apply scrolling
248   translation.x -= speed_x * elapsed_time;
249
250   keep_in_bounds();
251 }
252
253 void
254 Camera::scroll_autoscroll(float elapsed_time)
255 {
256   Player* player = sector->player;
257   
258   if(player->dying)
259     return;
260
261   if(auto_t - elapsed_time >= 0) {
262     translation += current_dir * elapsed_time;
263     auto_t -= elapsed_time;
264   } else {
265     // do the rest of the old movement
266     translation += current_dir * auto_t;
267     elapsed_time -= auto_t;
268     auto_t = 0;
269
270     // construct path for next point
271     if(auto_idx+1 >= scrollpoints.size()) {
272       keep_in_bounds();
273       return;
274     }
275     Vector distance = scrollpoints[auto_idx+1].position 
276                       - scrollpoints[auto_idx].position;
277     current_dir = distance.unit() * scrollpoints[auto_idx].speed;
278     auto_t = distance.norm() / scrollpoints[auto_idx].speed;
279
280     // do movement for the remaining time
281     translation += current_dir * elapsed_time;
282     auto_t -= elapsed_time;
283     auto_idx++;
284   }
285
286   keep_in_bounds();
287 }